一、前言
我们进行一个mybatis框架的基础代码可以如下:
@Test
public void test2() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUser(1);
log.info("user:{}", user);
}
在前面的章节中我们已经介绍了SqlSessionFactoryBuilder的build方法去说明如何解析我们的配置文件生成Configuration对象,而要执行对应的sql我们就要创建会话sqlSession。
二、会话创建源码解析
首先我们还是先来了解一下会话对象的组成结构如下图:
上面是会话的对象结构图,接下来我们将对他的创建过程的源码进行分析,剖析为啥会是这样子的结构。
2.1openSession源码解析
首先我们通过DefaultSqlSessionFactory的openSession来打开一个会话,具体源码如下:
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取Mybatis主配置文件配置的环境信息
final Environment environment = configuration.getEnvironment();
// 创建事务管理器工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务管理器
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据Mybatis主配置文件中指定的Executor类型创建对应的Executor实例
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession实例
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();
}
}
上面的逻辑也比较简单,首先获取Mybatis主配置文件配置的环境信息,然后创建事务管理器工厂以及创建事务管理器,然后根据Mybatis主配置文件中指定的Executor类型创建对应的Executor实例,mybatis有默认指定的defaultExecutorType,默认为ExecutorType.SIMPLE;
2.2 newExecutor源码分析
创建newExecutor实例的源码具体如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据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);
}
// 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 执行拦截器链的拦截逻辑
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
首先会根据根据executor类型创建对象的Executor对象,如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰,然后执行拦截器链的拦截逻辑。
三、 SQL 执行入口分析
在单独使用 MyBatis 进行数据库操作时,我们通常都会先调用 SqlSession 接口的 getMapper 方法为我们的 Mapper 接口生成实现类。然后就可以通过 Mapper 进行数据库操作。比如像下面这样:
com.jiagouedu.mapper.UserMapper mapper = sqlSession.getMapper(com.jiagouedu.mapper.UserMapper.class);
UserExample userExample = new UserExample();
UserExample.Criteria criteria = userExample.createCriteria();
criteria.andIdEqualTo(1L);
List userList = mapper.selectByExample(userExample);
log.info("user:{}", userList.get(0));
如果大家对 MyBatis 较为理解,会知道 SqlSession 是通过 JDK 动态代理的方式为接口生成代理对象的。在调用接口方法时,方法调用会被代理逻辑拦截。在代理逻辑中可根据方法名及方法归属接口获取到当前方法对应的 SQL 以及其他一些信息,拿到这些信息即可进行数据库操作。
2.1为 Mapper 接口创建代理对象
本节,我们从 DefaultSqlSession 的 getMapper 方法开始看起,如下:
// DefaultSqlSession
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
// Configuration
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
/**
* 根据Mapper接口Class对象获取Mapper动态代理对象
* @author maoqichuan
* @date 2021/12/28 15:11
* @return T
*/
// MapperRegistry
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
// 从 knownMappers 中获取与 type 对应的 MapperProxyFactor
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
大家可能不知道 knownMappers 集合中的元素是何时存入的。这里再说一遍吧,MyBatis 在解析配置文件的 节点的过程中,会调用 MapperRegistry 的 addMapper 方法将 Class 到 MapperProxyFactory 对象的映射关系存入到 knownMappers。具体的代码就不分析了。
在获取到 MapperProxyFactory 对象后,即可调用工厂方法为 Mapper 接口生成代理对象了。相关逻辑如下:
public T newInstance(SqlSession sqlSession) {
/*
* 创建 MapperProxy 对象,MapperProxy 实现了
* InvocationHandler 接口,代理逻辑封装在此类中
*/
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
// 通过 JDK 动态代理创建代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
上面的代码首先创建了一个 MapperProxy 对象,该对象实现了 InvocationHandler 接口。然后将对象作为参数传给重载方法,并在重载方法中调用 JDK 动态代理接口为 Mapper 生成代理对象。
2.1.1执行代理逻辑
在 MyBatis 中,Mapper 接口方法的代理逻辑实现的比较简单。该逻辑首先会对拦截的方法进行一些检测,以决定是否执行后续的数据库操作。对应的代码如下:
/**
* 首先检测被拦截的方法是不是定义在 Object 中的,比如 equals、hashCode 方法等。对于这类方法,直接执行即可。除此之外,
* MyBatis 从 3.4.2 版本开始,对 JDK 1.8 接口的默认方法提供了支持,具体就不分析了。完成相关检测后,紧接着从缓存中获取或者
* 创建 MapperMethod 对象,然后通过该对象中的 execute 方法执行 SQL。
* @author maoqichuan
* @date 2021/12/28 15:20
* @return java.lang.Object
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 从Object类继承的方法不做处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
/*
* 下面的代码最早出现在 mybatis-3.4.2 版本中,用于支持 JDK 1.8 中的
* 新特性 - 默认方法。这段代码的逻辑就不分析了,有兴趣的同学可以
* 去 Github 上看一下相关的相关的讨论(issue #709),链接如下:
*
* https://github.com/mybatis/mybatis-3/issues/709
*/
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 对Mapper接口中定义的方法进行封装,生成MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用 execute 方法执行 SQL
return mapperMethod.execute(sqlSession, args);
}
如上,代理逻辑会首先检测被拦截的方法是不是定义在 Object 中的,比如 equals、hashCode 方法等。对于这类方法,直接执行即可。除此之外,MyBatis 从 3.4.2 版本开始,对 JDK 1.8 接口的默认方法提供了支持,具体就不分析了。完成相关检测后,紧接着从缓存中获取或者创建 MapperMethod 对象,然后通过该对象中的 execute 方法执行 SQL。在分析 execute 方法之前,我们先来看一下 MapperMethod 对象的创建过程。MapperMethod 的创建过程看似普通,但却包含了一些重要的逻辑,所以不能忽视。
2.1.2 创建 MapperMethod 对象
本节来分析一下 MapperMethod 的构造方法,看看它的构造方法中都包含了哪些逻辑。如下:
/**
* @author Clinton Begin
* @author Eduardo Macarron
* @author Lasse Voss
* @author Kazuki Shimizu
*/
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
/**
* 主要是创建 SqlCommand 和 MethodSignature 对象。这两个对象分别记录了不同的信息,
* 这些信息在后续的方法调用中都会被用到
* @author maoqichuan
* @date 2021/12/28 15:26
*/
public MapperMethod(Class mapperInterface, Method method, Configuration config) {
// 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
this.command = new SqlCommand(config, mapperInterface, method);
// 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
this.method = new MethodSignature(config, mapperInterface, method);
}
}
如上,MapperMethod 构造方法的逻辑很简单,主要是创建 SqlCommand 和 MethodSignature 对象。这两个对象分别记录了不同的信息,这些信息在后续的方法调用中都会被用到。下面我们深入到这两个类的构造方法中,探索它们的初始化逻辑。
2.1.3构建SqlCommand对象
前面说了 SqlCommand 中保存了一些和 SQL 相关的信息,那具体有哪些信息呢?答案在下面的代码中。
public static class SqlCommand {
private final String name; // Mapper Id
private final SqlCommandType type; // SQL类型
public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
final String methodName = method.getName();
// 获取声明该方法的类或接口的Class对象
final Class declaringClass = method.getDeclaringClass();
// 获取描述标签的MappedStatement对象,解析 MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// 检测当前方法是否有对应的 MappedStatement
if (ms == null) {
// 检测当前方法是否有 @Flush 注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
/*
* 若 ms == null 且方法无 @Flush 注解,此时抛出异常。
* 这个异常比较常见,大家应该眼熟吧
*/
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName,
Class declaringClass, Configuration configuration) {
// 获取Mapper的Id
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
// 如果Configuration对象中已经注册了MappedStatement对象,则获取该MappedStatement对象
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 如果方法是在Mapper父接口中定义,则根据父接口获取对应的MappedStatement对象
for (Class superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
如上,SqlCommand 的构造方法主要用于初始化它的两个成员变量。代码不是很长,逻辑也不难理解,就不多说了。继续往下看。
2.1.4创建 MethodSignature 对象
MethodSignature 即方法签名,顾名思义,该类保存了一些和目标方法相关的信息。比如目标方法的返回类型,目标方法的参数列表信息等。下面,我们来分析一下 MethodSignature 的构造方法。
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
/**
* 用于检测目标方法的返回类型,以及解析目标方法参数列表。
* 其中,检测返回类型的目的是为避免查询方法返回错误的类型
* @author maoqichuan
* @date 2021/12/28 15:38
*/
public MethodSignature(Configuration configuration, Class mapperInterface, Method method) {
// 获取方法返回值类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class) {
this.returnType = (Class) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 返回值类型为void
this.returnsVoid = void.class.equals(this.returnType);
// 返回值类型为集合
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
// 返回值类型为Cursor
this.returnsCursor = Cursor.class.equals(this.returnType);
// 返回值类型为Optional
this.returnsOptional = Jdk.optionalExists && Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
// 返回值类型为Map
this.returnsMap = this.mapKey != null;
// RowBounds参数位置索引
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// ResultHandler参数位置索引
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// ParamNameResolver用于解析Mapper方法参数
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
// ...
}
上面的代码用于检测目标方法的返回类型,以及解析目标方法参数列表。其中,检测返回类型的目的是为避免查询方法返回错误的类型。比如我们要求接口方法返回一个对象,结果却返回了对象集合,这会导致类型转换错误。关于返回值类型的解析过程先说到这,下面分析参数列表的解析过程。
public ParamNameResolver(Configuration config, Method method) {
// 获取所有参数类型
final Class[] paramTypes = method.getParameterTypes();
// 获取所有参数注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap map = new TreeMap();
int paramCount = paramAnnotations.length;
// 从@Param 注解中获取参数名称
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
检测当前的参数类型是否为 RowBounds 或 ResultHandler
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 方法参数中,是否有Param注解
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 获取参数名称
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 未指定@Param 注解,这判断是否使用实际的参数名称,检测是否设置了 useActualParamName ,全局配置参考useActualParamName属性的作用
if (config.isUseActualParamName()) {
// 获取参数名
/*
* 通过反射获取参数名称。此种方式要求 JDK 版本为 1.8+,
* 且要求编译时加入 -parameters 参数,否则获取到的参数名
* 仍然是 arg1, arg2, ..., argN
*/
name = getActualParamName(method, paramIndex);
}
if (name == null) {
/*
* 使用 map.size() 返回值作为名称,思考一下为什么不这样写:
* name = String.valueOf(paramIndex);
* 因为如果参数列表中包含 RowBounds 或 ResultHandler,这两个参数
* 会被忽略掉,这样将导致名称不连续。
* 比如参数列表 (int p1, int p2, RowBounds rb, int p3)
* - 期望得到名称列表为 ["0", "1", "2"]
* - 实际得到名称列表为 ["0", "1", "3"]
*/
name = String.valueOf(map.size());
}
}
// 將参数信息存放在Map中,Key为参数位置索引,Value为参数名称
map.put(paramIndex, name);
}
// 將参数信息保存在names属性中
names = Collections.unmodifiableSortedMap(map);
}
以上就是方法参数列表的解析过程,解析完毕后,可得到参数下标到参数名的映射关系,这些映射关系最终存储在 ParamNameResolver 的 names 成员变量中。这些映射关系将会在后面的代码中被用到,大家留意一下。
2.1.5执行execute方法
面已经分析了 MapperMethod 的初始化过程,现在 MapperMethod 创建好了。那么,接下来要做的事情是调用 MapperMethod 的 execute 方法,执行 SQL。代码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 其中command为MapperMethod构造是创建的SqlCommand对象
// 获取SQL语句类型
switch (command.getType()) {
case INSERT: {
// 对用户传入的参数进行转换,下同
Object param = method.convertArgsToSqlCommandParam(args);
// 调用SqlSession的insert()方法,然后调用rowCountResult()方法统计行数
// 执行插入操作,rowCountResult 方法用于处理返回值
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 调用SqlSession对象的update()方法
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:
// 根据目标方法的返回类型进行相应的查询操作
if (method.returnsVoid() && method.hasResultHandler()) {
/*
* 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明使用者
* 想通过 ResultHandler 的方式获取查询结果,而非通过返回值获取结果
*/
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 执行查询操作,并返回多个结果
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 执行查询操作,并将结果封装在 Map 中返回
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 执行查询操作,并返回一个 Cursor 对象
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 = OptionalUtil.ofNullable(result);
}
}
break;
case FLUSH:
// 执行刷新操作
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常
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;
}
如上,execute 方法主要由一个 switch 语句组成,用于根据 SQL 类型执行相应的数据库操作。该方法的逻辑清晰,不需要太多的分析。不过在上面的方法中 convertArgsToSqlCommandParam 方法出现次数比较频繁,这里分析一下:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
/**
*
* A single non-special parameter is returned without a name.
* Multiple parameters are named using the naming rule.
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
*
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
/*
* 如果方法参数列表无 @Param 注解,且仅有一个非特别参数,则返回该参数的值。
* 比如如下方法:
* List findList(RowBounds rb, String name)
* names 如下:
* names = {1 : "0"}
* 此种情况下,返回 args[names.firstKey()],即 args[1] -> name
*/
return args[names.firstKey()];
} else {
final Map param = new ParamMap();
int i = 0;
for (Map.Entry entry : names.entrySet()) {
// 添加 <参数名, 参数值> 键值对到 param 中
param.put(entry.getValue(), args[entry.getKey()]);
// genericParamName = param + index。比如 param1, param2, ... paramN
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
/*
* 检测 names 中是否包含 genericParamName,什么情况下会包含?答案如下:
* 使用者显式将参数名称配置为 param1,即 @Param("param1")
*/
if (!names.containsValue(genericParamName)) {
// 添加 到 param 中
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
如上,convertArgsToSqlCommandParam 是一个空壳方法,该方法最终调用了 ParamNameResolver 的 getNamedParams 方法。getNamedParams 方法的主要逻辑是根据条件返回不同的结果,该方法的代码不是很难理解,我也进行了比较详细的注释,就不多说了。
2.2查询语句执行过程分析
查询语句对应的方法比较多,有如下几种:
executeWithResultHandler
executeForMany
executeForMap
executeForCursor
这些方法在内部调用了 SqlSession 中的一些 select* 方法,比如 selectList、selectMap、selectCursor 等。这些方法的返回值类型是不同的,因此对于每种返回类型,需要有专门的处理方法。以 selectList 方法为例,该方法的返回值类型为 List。但如果我们的 Mapper 或 Dao 的接口方法返回值类型为数组,或者 Set,直接将 List 类型的结果返回给 Mapper/Dao 就不合适了。execute* 等方法只是对 select* 等方法做了一层简单的封装,因此接下来我们应该把目光放在这些 select* 方法上。下面我们来分析一下 selectOne 方法的源码,如下:
2.2.1selectOne 方法分析
本节选择分析 selectOne 方法,而不是其他的方法,大家或许会觉得奇怪。前面提及了 selectList、selectMap、selectCursor 等方法,这里却分析一个未提及的方法。这样做并没什么特别之处,主要原因是 selectOne 在内部会调用 selectList 方法。这里分析 selectOne 方法是为了告知大家,selectOne 和 selectList 方法是有联系的,同时分析 selectOne 方法等同于分析 selectList 方法。
/**
* selectOne 方法在内部调用 selectList 了方法,并取 selectList 返回值的第1个元素作为自己的返回值。如果 selectList 返回的列表元素大于1
* @author maoqichuan
* @date 2021/12/28 16:03
*/
@Override
public T selectOne(String statement, Object parameter) {
// 调用 selectList 获取结果
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
// 如果查询结果大于1则抛出异常,这个异常也是很常见的
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
如上,selectOne 方法在内部调用 selectList 了方法,并取 selectList 返回值的第1个元素作为自己的返回值。如果 selectList 返回的列表元素大于1,则抛出异常。上面代码比较易懂,就不多说了。下面我们来看看 selectList 方法的实现。
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根据Mapper的Id,获取对应的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 以MappedStatement对象作为参数,调用Executor的query()方法
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();
}
}
如上,这里要来说说 executor 变量,该变量类型为 Executor。Executor 是一个接口,它的实现类如下:
在上面我们也知道了默认情况下,executor 的类型为 CachingExecutor,该类是一个装饰器类,用于给目标 Executor 增加二级缓存功能。那目标 Executor 是谁呢?默认情况下是 SimpleExecutor。
现在大家搞清楚 executor 变量的身份了,接下来继续分析 selectOne 方法的调用栈。先来看看 CachingExecutor 的 query 方法是怎样实现的。如下:
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取boundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 调用createCacheKey()方法创建缓存Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用重载方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上面的方法和 SimpleExecutor 父类 BaseExecutor 中的实现没什么区别,有区别的地方在于这个方法所调用的重载方法。我们继续往下看。
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取MappedStatement对象中维护的二级缓存对象
Cache cache = ms.getCache();
// 若映射文件中未配置缓存或参照缓存,此时 cache = null
if (cache != null) {
// 判断是否需要刷新二级缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 从MappedStatement对象对应的二级缓存中获取数据
@SuppressWarnings("unchecked")
List list = (List) tcm.getObject(cache, key);
if (list == null) {
// 若缓存未命中,则调用被装饰类的 query 方法
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 將数据存放到MappedStatement对象对应的二级缓存中
tcm.putObject(cache, key, list);
}
return list;
}
}
// 调用被装饰类的 query 方法
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
面的代码涉及到了二级缓存,若二级缓存为空,或未命中,则调用被装饰类的 query 方法。下面来看一下 BaseExecutor 的中签名相同的 query 方法是如何实现的。
@SuppressWarnings("unchecked")
@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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
queryStack++;
// 从一级缓存中获取结果
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存中获取不到,则调用queryFromDatabase()方法从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// 从一级缓存中延迟加载嵌套查询结果
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
如上,上面的方法主要用于从一级缓存中查找查询结果。若缓存未命中,再向数据库进行查询。在上面的代码中,出现了一个新的类 DeferredLoad,这个类用于延迟加载。接下来,我们来看一下 queryFromDatabase 方法的实现。
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用doQuery()方法查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 缓存查询结果
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
上面的代码仍然不是 selectOne 方法调用栈的终点,抛开缓存操作,queryFromDatabase 最终还会调用 doQuery 进行查询。下面我们继续进行跟踪。
@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 获取StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 调用prepareStatement()方法,创建Statement对象,并进行设置参数等操作
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler对象的query()方法执行查询操作
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上面的方法中仍然有不少的逻辑,完全看不到即将要到达终点的趋势,不过这离终点又近了一步。接下来,我们先跳过 StatementHandler 和 Statement 创建过程,这两个对象的创建过程会在后面进行说明。这里,我们以 PreparedStatementHandler 为例,看看它的 query 方法是怎样实现的。如下:
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 调用PreparedStatement对象的execute()方法,执行SQL语句
ps.execute();
// 调用ResultSetHandler的handleResultSets()方法处理结果集
return resultSetHandler. handleResultSets(ps);
}
到这里mybaits的sql执行流程差不多分析完了,整个流程还有很多细节以及逻辑,大家有兴趣的可以继续深入去学习。
你可能感兴趣的:(Mybatis源码解析,java,mybatis源码分析,sql执行流程分析,mybatis一二级缓存)
Python连接数据库汇总(二)
xinxiyinhe
python python 数据库 开发语言
以下是Python调用其他数据库的代码示例:1.MicrosoftSQLServer#安装库:pipinstallpyodbcimportpyodbc#连接数据库conn=pyodbc.connect('DRIVER={ODBCDriver17forSQLServer};''SERVER=localhost;''DATABASE=test_db;''UID=sa;''PWD=your_passwo
netty做一个posp的网络_Java网络通信基础系列-Netty实现HTTP服务
weixin_39748928
netty做一个posp的网络
一.Netty实现HTTP服务HTTP程序开发:在进行WEB开发过程之中,HTTP是主要的通讯协议,但是你千万要记住一个问题,HTTP都是基于TCP协议的一种应用,HTTP是在TCP的基础上完善出来的。TCP是一种可靠的连接协议,所以TCP的执行性能未必会高。据说google正在开发HTTP3.0技术标准,并且这个技术里面将使用UDP协议作为HTTP基础协议。HTTP里面存在有请求模式:A:HTT
python远程连接mysql数据库_python远程连接MySQL数据库
weixin_39528697
python远程连接MySQL数据库本文实例为大家分享了python远程连接MySQL数据库的具体代码,供大家参考,具体内容如下连接数据库这里默认大家都已经配置安装好MySQL和Python的MySQL模块,且默认大家的DB内表和访问账号权限均已设置无误,下面直接代码演示:#-*-coding:utf-8-*-"""CreatedonFriDec3010:43:352016@author:zhen
【Python实用教学篇】手把手4步教会你用Python连接数据库!
田野猫咪
数据库 python 开发语言
一,打开数据库(Mysql)服务二,用Sqlyog(回复yog获取sqlyog工具使用方法)连接自己要测试的数据库,创建测试用数据库和表三,打开PyCharm,(python开发2.*版本可以直接使用MySQL,python3.*版本需要下载使用PyMySQL包才能连接数据库),按照下图方法安装PyMySql包1.ctrl+alt+s调出设置面板,选择project下的pythoninterpre
C语言(11)
life_time_
c语言 开发语言
一.分支与循环1.if语句:1.1语法形式if(表达式)语句表达式为真,语句执行;表达式为假,语句便不会执行。(C语言,0为假,1为真)1.2else语句语法形式:if(表达式)语句1else语句2注释:默认下,if和else后边只能跟一条语句,若使用多条,需要使用{},便可在{}内使用多条语句。1.3嵌套ifelse可以与另一个if语句连用,构成多重判断。例如图一图一1.4悬空else问题els
Python编程基础14:数据库编程
酒城译痴无心剑
与Python共舞红尘 数据库 python sqlite
文章目录一、关系数据库(一)数据模型1、层次模型(一对多)2、网状模型(多对多)3、关系模型(一对一)4、面向对象模型(二)关系数据库的概念和特点1、基本概念(1)关系(2)二维表(3)记录与字段(4)关键字(5)外部关键字2、关系数据库的基本特点(三)关系数据库语言SQL1、创建和删除数据库(1)创建数据库:使用createdatabase语句(2)删除数据库:使用dropdatabase语句2
python 代码连接数据库汇总(一)
xinxiyinhe
python python 数据库 开发语言
以下是Python调用常见数据库的代码示例:1.MySQL#安装库:pipinstallmysql-connector-pythonimportmysql.connector#连接数据库conn=mysql.connector.connect(host="localhost",user="root",password="your_password",database="test_db")curso
JS事件冒泡和事件捕获
一朵好运莲
javascript 前端 开发语言
在JavaScript中,事件冒泡(EventBubbling)和事件捕获(EventCapturing)是两种不同的事件传播机制。当你在DOM元素上注册事件时,事件的触发并不会立即发生,而是会按照一定的顺序进行传播。事件的传播机制决定了事件如何从目标元素传递到DOM树中的其他元素。目录事件捕获和事件冒泡的提出为什么整合了捕获和冒泡机制?事件传播的顺序具体例子实现事件捕获和事件冒泡的提出事件冒泡最
数据库-python SQLite3
45度看我
数据库 python 数据库
数据库-pythonSQLite3一:sqlite3简介二:sqlite3流程1>demo2>sqlite3流程三:sqlite3step1>createtable2>insertinto3>update4>select1.fetchall()2.fetchone()3.fetchmany()5>delete6>otherstep四:Mysql1>Mysql知识详解一:sqlite3简介sqlit
深入解析CSS动画:从基础到实战的完整指南
斯~内克
css css 前端
一、CSS动画的现代意义与核心优势在Web开发领域,CSS动画已成为构建现代交互体验的核心技术。相比传统的JavaScript动画实现方式,CSS动画具有以下显著优势:硬件加速优化:浏览器可自动使用GPU加速,实现更流畅的动画效果声明式语法:通过简洁的代码描述复杂动画序列性能优势:浏览器原生支持,无需额外脚本解析维护便捷:动画逻辑与业务代码分离,易于修改和扩展响应式支持:可结合媒体查询实现多设备适
前端 CDN 深度解析:从加速优化到工程化实践
斯~内克
前端 前端
一、前端CDN的核心价值与挑战1.1现代前端资源的分发困境资源体积爆炸:主流框架生产包平均大小突破2MB全球化访问需求:跨国访问延迟差异可达500ms以上版本管理复杂度:多环境部署导致的缓存污染问题安全防护压力:XSS、资源劫持等攻击频发1.2前端CDN的技术指标指标传统方案CDN优化后提升幅度首屏加载时间3.8s1.2s68%资源下载速度12MB/s85MB/s608%缓存命中率65%98%51
Pandas使用教程 - Pandas 与 SQL 数据库交互
闲人编程
Pandas使用教程 数据库 pandas sql 数据分析 存储 加载数据
目录进阶篇40.Pandas与SQL数据库交互1.引言2.数据读取:从SQL加载数据2.1使用pd.read_sql()示例:使用SQLAlchemy连接SQLite数据库2.2使用pd.read_sql_table()3.数据写入:将DataFrame写入SQL数据库3.1使用DataFrame.to_sql()示例:写入数据到SQLite数据库4.数据库连接与SQLAlchemy4.1使用SQ
基于SpringBoot的医院电子病历系统设计与实现(源码+万字报告+讲解)
炳烛之明科技
spring boot 后端 java
目录摘要IABSTRACTII第一章绪论11.1研究背景与意义11.2国内外研究现状11.3研究内容2第二章相关技术工具简介32.1Java语言32.2SpringBoot框架32.3MySQL数据库3第三章系统需求分析43.1可行性分析43.2系统总体功能需求分析43.3管理员总体功能需求分析43.4患者总体功能需求分析63.5医生总体功能需求分析6第四章系统设计84.1系统的总体设计84.2系
MES基于C#开发计划管理组件集成WinCC中使用
dephixf
MOM相关 c# 制造
MES系统通常都会与SCADA系统集成使用,尤其具备中控管理需求的工厂,集控SCADA需要集成MES系统的计划工单数据实现工单数据、工艺配方数据、SN数据、打码数据等下发,执行状态、关键参数反馈等上传。本文介绍在WinCC画面集成基于C#开发的计划管理界面,以下代码主要是C#开发计划管理组件代码。项目代码:https://download.csdn.net/download/bjhtgy/8969
SQL语句如何把一张表的某列数据更新另外一张表的某列
dephixf
数据库 mysql sql
在SQL中,可以使用多种方法将一个表的数据列更新为另一个表的数据列。以下是一些常见的方法:一、使用UPDATE和JOIN假设我们有两个表table1和table2,想要将table2中的某一列数据更新到table1的对应列中,可以使用以下SQL语句:UPDATEtable1JOINtable2ONtable1.join_column=table2.join_columnSETtable1.targ
数据结构之队列,哈希表
不知真不只
数据结构 散列表
一队列(先进先出)1.定义:从一端进行数据插入,另一端进行删除的线性存储结构队列类型常见操作-入队(Enqueue):将新元素添加到队列的尾部。若队列有空间,新元素会成为队列的新尾部元素;若队列已满,可能会触发队列已满的处理机制。-出队(Dequeue):从队列的头部移除元素。执行后,原队头元素被删除,原队头的下一个元素成为新队头。若队列为空,可能会触发队列空的处理机制。-获取队头元素(Front
第三百七十三节 JavaFX教程 - JavaFX分页
程序猿小D
vr java eclipse intellij-idea javascript
JavaFX教程-JavaFX分页分页控件用于浏览多个页面。我们典型地使用对网页的分页控制,例如博客。在博客页面的底部,我们可以看到一个矩形区域,作为一个数字列表来指示页面索引,以及一个下一个/上一个按钮来链接到下一个/上一个页面。创建分页控件分页控件由页面内容和页面导航区域组成。创建具有不确定页计数和当前页索引等于零的分页控件Paginationpagination1=newPagination
自学嵌入式第29天-----epoll、sqlite3
以德服人23
服务器 网络 运维
1.正确选择触发模式(ET和LT)水平触发(LT):默认模式,只要文件描述符处于就绪状态,epoll_wait会持续通知。适合大多数场景,编程简单。边缘触发(ET):只在状态变化时通知一次,适合高性能场景,但需要确保一次性处理完所有数据,否则可能丢失事件。注意:在ET模式下,必须循环读取或写入数据,直到返回EAGAIN或EWOULDBLOCK。如果未处理完数据,epoll_wait不会再通知,可能
JVM类加载
zhujilisa
java jvm
JVM类加载类加载类加载器类加载加载:查找并加载类的字节码文件验证:确保加载的字节码是合法且符合JVM规范准备:给类的静态变量分配内存,并赋默认值解析:符号引用替换为直接引用初始化:静态变量赋值,执行静态代码块类加载器引导类加载器(BootstrapClassLoader):加载JVM核心类库(如rt.jar中的类),位于JAVA_HOME/lib目录下扩展类加载器(ExtClassLoader)
Java GC的常用算法
yyueshen
JVM java jvm
在Java中,垃圾回收(GarbageCollection,GC)是自动内存管理的核心机制,以下是几种常用的JavaGC算法:1.标记-清除算法(Mark-Sweep)原理标记阶段:从根对象(如虚拟机栈中的引用对象、静态变量引用的对象等)开始遍历,标记所有可达对象。清除阶段:遍历整个堆,将未标记的对象(即不可达对象)所占的内存空间回收。优缺点优点:实现简单,不需要额外的空间。缺点:会产生大量的内存
JVM的垃圾回收器都有哪些?
yyueshen
jvm 测试工具
在Java虚拟机(JVM)中,不同的垃圾回收器采用不同的算法和策略,以满足不同应用场景的性能需求。以下为你详细介绍常见的JVM垃圾回收器:新生代垃圾回收器1.Serial收集器特点:单线程的垃圾回收器,在进行垃圾回收时,必须暂停其他所有的工作线程(StopTheWorld,简称STW),直到垃圾回收完成。适用场景:适用于客户端模式下的小型应用程序,因为它的实现简单,没有线程交互的开销,在单CPU环
常见JVM命令
yyueshen
JVM jvm java
1.java-XX:+PrintCommandLineFlagsHelloGC作用:打印JVM启动时的命令行参数,包括用户显式设置的参数和JVM自动默认设置的参数。用于确认JVM实际使用的配置。2.java-Xmn10M-Xms40M-Xmx60M-XX:+PrintCommandLineFlags-XX:+PrintGC-XX:+PrintGCDetails-XX:+PrintGCTimeSta
JAVA面试_进阶部分_java中四种引用类型(对象的强、软、弱和虚引用)
茂茂在长安
JAVA java 面试 jvm
java中四种引用类型(对象的强、软、弱和虚引用)对象的强、软、弱和虚引用在JDK1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。⑴强引用(Stro
存储优化(protobuf与mmkv)
Ya-Jun
android kotlin
存储优化(protobuf与mmkv)在Android应用开发中,数据存储是一个基础且关键的环节。随着应用功能的日益复杂,数据量的增加,传统的存储方式如SharedPreferences、SQLite等在性能上的局限性逐渐显现。本文将深入探讨两种高效的存储优化方案:ProtocolBuffers(protobuf)和MMKV,帮助开发者构建更高效、更可靠的数据存储系统。一、传统存储方式的局限性在讨
【前端】【nuxt】几种在 Nuxt 客户端使用console的方式
患得患失949
Nuxt 面试考题专栏(前后端) 前端 Nuxt console.log 客户端
方法1:在Vue生命周期钩子中使用只在客户端执行的钩子(如mounted)中打印:exportdefault{mounted(){console.log('仅在客户端显示',this.$route.path)}}方法2:通过环境判断使用process.client判断当前环境:if(process.client){console.log('客户端环境变量:',process.env.NODE_EN
pnpm安装报错
魏时烟
工作日志 npm 前端 node.js
mac安装pnpm成功,查看版本报错pnpm:commandnotfound执行命令//查看npm默认安装路径npmconfiggetprefix//1.新建一个全局安装的路径mkdir~/.npm-global//2.配置npm使用新的路径npmconfigsetprefix'~/.npm-global'//3.打开或者新建~/.profile,(vi~/.profile)加入下面一行expor
深度解析volatile—底层实现
ljheee
Java虚拟机 JVM进阶 JVM volatile java内存可见性
我们都知道,Java关键字volatile的作用1、内存可见性2、禁止指令重排序可见性是指,在多线程环境,共享变量的操作对于每个线程来说,都是内存可见的,也就是每个线程获取的volatile变量都是最新值;并且每个线程对volatile变量的修改,都直接刷新到主存。下面重点介绍指令重排序。为什么要指令重排序?为了提高程序执行的性能,编译器和执行器(处理器)通常会对指令做一些优化(重排序)1、编译器
动态代理背后的魔法:Spring AOP执行链路解析与自定义扩展模板
周小闯
Spring spring java 后端
动态代理背后的魔法:SpringAOP执行链路解析与自定义扩展模板一、SpringAOP简介面向切面编程(AOP)是一种通过横向抽取横切关注点(如日志、事务、权限等)来提升代码模块化的技术。SpringAOP基于动态代理实现,通过注解或XML配置简化切面定义,支持方法级别的增强,其核心优势在于非侵入性和声明式编程。二、核心注解详解SpringAOP的注解驱动开发是主流实践,通过以下注解实现切面逻辑
[特殊字符] Elasticsearch 双剑合璧:HTTP API 与 Java API 实战整合指南
周小闯
elasticsearch 项目实战 elasticsearch http java
Elasticsearch双剑合璧:HTTPAPI与JavaAPI实战整合指南一、HTTPAPI定义与用途Elasticsearch的HTTPAPI是基于RESTful接口设计的核心交互方式,支持通过URL和JSON数据直接操作索引、文档、集群等资源。适用于快速调试、脚本调用和跨语言集成。1.索引管理(1)创建索引(指定分片与映射)PUT/products{"settings":{"number_
npm --unsafe-perm 参数,解决权限错误,permission denied
你的微笑像拥抱
笔记
https://docs.npmjs.com/misc/config#unsafe-perm解决办法:添加–unsafe-perm参数,如#npminstallXXX--registry=https://registry.npm.taobao.org--unsafe-permnpm会有生命周期,某个包会有生命周期来执行一些东西,安全起见会自动降级导致没有权限执行一些操作,通过–unsafe-per
Maven
Array_06
eclipse jdk maven
Maven
Maven是基于项目对象模型(POM), 信息来管理项目的构建,报告和文档的软件项目管理工具。
Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具。由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目。由于 Maven 的面向项目的方法,许多 Apache Jakarta 项目发文时使用 Maven,而且公司
ibatis的queyrForList和queryForMap区别
bijian1013
java ibatis
一.说明
iBatis的返回值参数类型也有种:resultMap与resultClass,这两种类型的选择可以用两句话说明之:
1.当结果集列名和类的属性名完全相对应的时候,则可直接用resultClass直接指定查询结果类
LeetCode[位运算] - #191 计算汉明权重
Cwind
java 位运算 LeetCode Algorithm 题解
原题链接:#191 Number of 1 Bits
要求:
写一个函数,以一个无符号整数为参数,返回其汉明权重。例如,‘11’的二进制表示为'00000000000000000000000000001011', 故函数应当返回3。
汉明权重:指一个字符串中非零字符的个数;对于二进制串,即其中‘1’的个数。
难度:简单
分析:
将十进制参数转换为二进制,然后计算其中1的个数即可。
“
浅谈java类与对象
15700786134
java
java是一门面向对象的编程语言,类与对象是其最基本的概念。所谓对象,就是一个个具体的物体,一个人,一台电脑,都是对象。而类,就是对象的一种抽象,是多个对象具有的共性的一种集合,其中包含了属性与方法,就是属于该类的对象所具有的共性。当一个类创建了对象,这个对象就拥有了该类全部的属性,方法。相比于结构化的编程思路,面向对象更适用于人的思维
linux下双网卡同一个IP
被触发
linux
转自:
http://q2482696735.blog.163.com/blog/static/250606077201569029441/
由于需要一台机器有两个网卡,开始时设置在同一个网段的IP,发现数据总是从一个网卡发出,而另一个网卡上没有数据流动。网上找了下,发现相同的问题不少:
一、
关于双网卡设置同一网段IP然后连接交换机的时候出现的奇怪现象。当时没有怎么思考、以为是生成树
安卓按主页键隐藏程序之后无法再次打开
肆无忌惮_
安卓
遇到一个奇怪的问题,当SplashActivity跳转到MainActivity之后,按主页键,再去打开程序,程序没法再打开(闪一下),结束任务再开也是这样,只能卸载了再重装。而且每次在Log里都打印了这句话"进入主程序"。后来发现是必须跳转之后再finish掉SplashActivity
本来代码:
// 销毁这个Activity
fin
通过cookie保存并读取用户登录信息实例
知了ing
JavaScript html
通过cookie的getCookies()方法可获取所有cookie对象的集合;通过getName()方法可以获取指定的名称的cookie;通过getValue()方法获取到cookie对象的值。另外,将一个cookie对象发送到客户端,使用response对象的addCookie()方法。
下面通过cookie保存并读取用户登录信息的例子加深一下理解。
(1)创建index.jsp文件。在改
JAVA 对象池
矮蛋蛋
java ObjectPool
原文地址:
http://www.blogjava.net/baoyaer/articles/218460.html
Jakarta对象池
☆为什么使用对象池
恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率。Jakarta Commons Pool组件提供了一整套用于实现对象池化
ArrayList根据条件+for循环批量删除的方法
alleni123
java
场景如下:
ArrayList<Obj> list
Obj-> createTime, sid.
现在要根据obj的createTime来进行定期清理。(释放内存)
-------------------------
首先想到的方法就是
for(Obj o:list){
if(o.createTime-currentT>xxx){
阿里巴巴“耕地宝”大战各种宝
百合不是茶
平台战略
“耕地保”平台是阿里巴巴和安徽农民共同推出的一个 “首个互联网定制私人农场”,“耕地宝”由阿里巴巴投入一亿 ,主要是用来进行农业方面,将农民手中的散地集中起来 不仅加大农民集体在土地上面的话语权,还增加了土地的流通与 利用率,提高了土地的产量,有利于大规模的产业化的高科技农业的 发展,阿里在农业上的探索将会引起新一轮的产业调整,但是集体化之后农民的个体的话语权 将更少,国家应出台相应的法律法规保护
Spring注入有继承关系的类(1)
bijian1013
java spring
一个类一个类的注入
1.AClass类
package com.bijian.spring.test2;
public class AClass {
String a;
String b;
public String getA() {
return a;
}
public void setA(Strin
30岁转型期你能否成为成功人士
bijian1013
成功
很多人由于年轻时走了弯路,到了30岁一事无成,这样的例子大有人在。但同样也有一些人,整个职业生涯都发展得很优秀,到了30岁已经成为职场的精英阶层。由于做猎头的原因,我们接触很多30岁左右的经理人,发现他们在职业发展道路上往往有很多致命的问题。在30岁之前,他们的职业生涯表现很优秀,但从30岁到40岁这一段,很多人
[Velocity三]基于Servlet+Velocity的web应用
bit1129
velocity
什么是VelocityViewServlet
使用org.apache.velocity.tools.view.VelocityViewServlet可以将Velocity集成到基于Servlet的web应用中,以Servlet+Velocity的方式实现web应用
Servlet + Velocity的一般步骤
1.自定义Servlet,实现VelocityViewServl
【Kafka十二】关于Kafka是一个Commit Log Service
bit1129
service
Kafka is a distributed, partitioned, replicated commit log service.这里的commit log如何理解?
A message is considered "committed" when all in sync replicas for that partition have applied i
NGINX + LUA实现复杂的控制
ronin47
lua nginx 控制
安装lua_nginx_module 模块
lua_nginx_module 可以一步步的安装,也可以直接用淘宝的OpenResty
Centos和debian的安装就简单了。。
这里说下freebsd的安装:
fetch http://www.lua.org/ftp/lua-5.1.4.tar.gz
tar zxvf lua-5.1.4.tar.gz
cd lua-5.1.4
ma
java-14.输入一个已经按升序排序过的数组和一个数字, 在数组中查找两个数,使得它们的和正好是输入的那个数字
bylijinnan
java
public class TwoElementEqualSum {
/**
* 第 14 题:
题目:输入一个已经按升序排序过的数组和一个数字,
在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是 O(n) 。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组 1 、 2 、 4 、 7 、 11 、 15 和数字 15 。由于
Netty源码学习-HttpChunkAggregator-HttpRequestEncoder-HttpResponseDecoder
bylijinnan
java netty
今天看Netty如何实现一个Http Server
org.jboss.netty.example.http.file.HttpStaticFileServerPipelineFactory:
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast(&quo
java敏感词过虑-基于多叉树原理
cngolon
违禁词过虑 替换违禁词 敏感词过虑 多叉树
基于多叉树的敏感词、关键词过滤的工具包,用于java中的敏感词过滤
1、工具包自带敏感词词库,第一次调用时读入词库,故第一次调用时间可能较长,在类加载后普通pc机上html过滤5000字在80毫秒左右,纯文本35毫秒左右。
2、如需自定义词库,将jar包考入WEB-INF工程的lib目录,在WEB-INF/classes目录下建一个
utf-8的words.dict文本文件,
多线程知识
cuishikuan
多线程
T1,T2,T3三个线程工作顺序,按照T1,T2,T3依次进行
public class T1 implements Runnable{
@Override
spring整合activemq
dalan_123
java spring jms
整合spring和activemq需要搞清楚如下的东东1、ConnectionFactory分: a、spring管理连接到activemq服务器的管理ConnectionFactory也即是所谓产生到jms服务器的链接 b、真正产生到JMS服务器链接的ConnectionFactory还得
MySQL时间字段究竟使用INT还是DateTime?
dcj3sjt126com
mysql
环境:Windows XPPHP Version 5.2.9MySQL Server 5.1
第一步、创建一个表date_test(非定长、int时间)
CREATE TABLE `test`.`date_test` (`id` INT NOT NULL AUTO_INCREMENT ,`start_time` INT NOT NULL ,`some_content`
Parcel: unable to marshal value
dcj3sjt126com
marshal
在两个activity直接传递List<xxInfo>时,出现Parcel: unable to marshal value异常。 在MainActivity页面(MainActivity页面向NextActivity页面传递一个List<xxInfo>): Intent intent = new Intent(this, Next
linux进程的查看上(ps)
eksliang
linux ps linux ps -l linux ps aux
ps:将某个时间点的进程运行情况选取下来
转载请出自出处:http://eksliang.iteye.com/admin/blogs/2119469
http://eksliang.iteye.com
ps 这个命令的man page 不是很好查阅,因为很多不同的Unix都使用这儿ps来查阅进程的状态,为了要符合不同版本的需求,所以这个
为什么第三方应用能早于System的app启动
gqdy365
System
Android应用的启动顺序网上有一大堆资料可以查阅了,这里就不细述了,这里不阐述ROM启动还有bootloader,软件启动的大致流程应该是启动kernel -> 运行servicemanager 把一些native的服务用命令启动起来(包括wifi, power, rild, surfaceflinger, mediaserver等等)-> 启动Dalivk中的第一个进程Zygot
App Framework发送JSONP请求(3)
hw1287789687
jsonp 跨域请求 发送jsonp ajax请求 越狱请求
App Framework 中如何发送JSONP请求呢?
使用jsonp,详情请参考:http://json-p.org/
如何发送Ajax请求呢?
(1)登录
/***
* 会员登录
* @param username
* @param password
*/
var user_login=function(username,password){
// aler
发福利,整理了一份关于“资源汇总”的汇总
justjavac
资源
觉得有用的话,可以去github关注:https://github.com/justjavac/awesome-awesomeness-zh_CN 通用
free-programming-books-zh_CN 免费的计算机编程类中文书籍
精彩博客集合 hacke2/hacke2.github.io#2
ResumeSample 程序员简历
用 Java 技术创建 RESTful Web 服务
macroli
java 编程 Web REST
转载:http://www.ibm.com/developerworks/cn/web/wa-jaxrs/
JAX-RS (JSR-311) 【 Java API for RESTful Web Services 】是一种 Java™ API,可使 Java Restful 服务的开发变得迅速而轻松。这个 API 提供了一种基于注释的模型来描述分布式资源。注释被用来提供资源的位
CentOS6.5-x86_64位下oracle11g的安装详细步骤及注意事项
超声波
oracle linux
前言:
这两天项目要上线了,由我负责往服务器部署整个项目,因此首先要往服务器安装oracle,服务器本身是CentOS6.5的64位系统,安装的数据库版本是11g,在整个的安装过程中碰到很多的坑,不过最后还是通过各种途径解决并成功装上了。转别写篇博客来记录完整的安装过程以及在整个过程中的注意事项。希望对以后那些刚刚接触的菜鸟们能起到一定的帮助作用。
安装过程中可能遇到的问题(注
HttpClient 4.3 设置keeplive 和 timeout 的方法
supben
httpclient
ConnectionKeepAliveStrategy kaStrategy = new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
long keepAlive
Spring 4.2新特性-@Import注解的升级
wiselyman
spring 4
3.1 @Import
@Import注解在4.2之前只支持导入配置类
在4.2,@Import注解支持导入普通的java类,并将其声明成一个bean
3.2 示例
演示java类
package com.wisely.spring4_2.imp;
public class DemoService {
public void doSomethin