后端用Integer接收0传入param.pushStatus,为什么param.pushStatus !=''判断为false
后端使用Integer接收
mapper接口传入的参数类型为Integer值为0时,mybaits 在进行 **param.pushStatus !=‘’**的时候会默认""和0 都转换成double进行比较 都是0.0 ,结果不是重点,重点在于下面过程。
Mybatis-plus很多类重写了Mybatis,此处以Mybatis-plus源码出发
项目启动会通过springboot的自动装配原理加载MybatisPlusAutoConfiguration从而加载SqlSessionFactory(加载mapper到MybatisSqlSessionFactoryBean)
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
...
if (StringUtils.hasText(this.properties.getConfigLocation())) {
//下面的ConfigLocation为:classpath:mybatis/mybatis-config.xml
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
...
//这里是装载插件(之后会加入责任链中)适用于慢sql查询拦截器等
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
...
//...注入很多属性比如:自定义枚举包、注入主键生成器、注入sql注入器、注入ID生成器等
factory.getObject();
}
//factory.getObject()--会进行后置属性设置(MybatisSqlSessionFactoryBean)
//MybatisSqlSessionFactoryBean
//这里关注mapper的设置
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
MybatisXMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
...
} else if (this.configLocation != null) {
// this.configLocation 里面包括mybatis/mybatis-config.xml
//如果在配置文件有标签解析
xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
...
}
......
if (xmlConfigBuilder != null) {
try {
//第一种--解析配置文件mybatis-config.xml信息
xmlConfigBuilder.parse();
}
......
//mapperLocations是提前扫描自定义的classpath:mapper/*.xml文件
//例如:file [E:\code\trade\trade\target\classes\mapper\SellReconciliationMapper.xml]
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
...
} else {
//循环遍历所有的xxxx.xml
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
//这里解析mapper
xmlMapperBuilder.parse();
}
}
}
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//将mybatis-config.xml通过XPath解析成XNode再进行解析
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//这里可以配置mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//前面已经获取了SellReconciliationMapper.xml,通过命名空间解析mapper
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//再通过命名空间绑定mapper
bindMapperForNamespace();
}
//解析ResultMap等属性
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
//com.jdh.trade.mapper.SellReconciliationMapper
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//获取命名空间获取mapper接口
boundType = Resources.classForName(namespace);
}
if (boundType != null && !configuration.hasMapper(boundType)) {
//Spring可能不知道真正的资源名,所以我们设置了一个标志
//防止从映射器接口再次加载这个资源
configuration.addLoadedResource("namespace:" + namespace);
//关键这里将mapper增加到map中
configuration.addMapper(boundType);
}
}
}
//key mapper接口 value mapper代理工厂
//private final Map, MybatisMapperProxyFactory>> knownMappers = new HashMap<>();
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
...
boolean loadCompleted = false;
try {
//!!!!保存到map!!!!
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
//在运行解析器之前添加类型是很重要的否则,将自动尝试绑定映射器解析器。如果类型已经知道,则不会尝试.
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
}...
}
@service 加载bean之后会通过
doGetObjectFromFactoryBean方法中执行factory.getObject()获取到bean实例MybatisMapperProxy。
//MapperFactoryBean
//对于mybatis相关的mapper
@Override
public T getObject() throws Exception {
//this.mapperInterface 相当于接口com.jdh.trade.mapper.xxxMapper
return getSqlSession().getMapper(this.mapperInterface);
}
//SqlSessionTemplate
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
//MybatisMapperRegistry
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//knownMappers 这个map是上面保存的
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
...
try {
//生成代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//MybatisMapperProxyFactory
//mapperProxyFactory.newInstance(sqlSession);
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
//Proxy 生成动态代理实例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
执行mapper的查询等语句就会进入代理对象的invoke方法
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
MybatisMapperProxy
从缓存获取MybatisMapperMethod-属性如下
最终执行sql走的是MybatisMapperMethod.execute(sqlSession, args)
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根据是查询还是更新进入不同分支
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//比如 selectList 就会走这里
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 分页查询
if (IPage.class.isAssignableFrom(method.getReturnType())) {
...
//关注这里 执行分页 默认也是执行selectList
result = executeForIPage(sqlSession, args);
...
}
}
/**
* TODO IPage 专用
*/
private <E> List<E> executeForIPage(SqlSession sqlSession, Object[] args) {
Object param = method.convertArgsToSqlCommandParam(args);
//执行SqlSessionTemplate的selectList
return sqlSession.selectList(command.getName(), param);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
//DefaultSqlSession
return this.sqlSessionProxy.selectList(statement, parameter);
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//加载sqlSession-getSqlSession-通过sessionFactory.openSession(executorType);
//从中会获取执行器和加载插件
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//拿到sqlSession才能去执行查询
Object result = method.invoke(sqlSession, args);
....
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
...
//DefaultSqlSessionFactory.openSession
session = sessionFactory.openSession(executorType);
...
return session;
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//新建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器 MybatisConfiguration.newExecutor
//(默认是simple执行器)
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();
}
}
@Override
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 MybatisBatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new MybatisReuseExecutor(this, transaction);
} else {
executor = new MybatisSimpleExecutor(this, transaction);
}
if (cacheEnabled) {
//装饰者模式 装饰了简单执行器
executor = new MybatisCachingExecutor(executor);
}
//责任链 进行增加所有执行器 并执行plugin
//通过判断插件,new Plugin(target, interceptor, signatureMap))生成MybatisCachingExecutor代理对象!!!
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
DefaultSqlSession.selectList
//DefaultSqlSession
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
//rowBounds 是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
//wrapCollection(parameter)是用来装饰集合或者数组参数 里面有查询条件
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//statement:
//名字com.jdh.trade.mapper.SellReconciliationMapper.selectSellReconciliationByPage
//获取MappedStatement对象,通过配置信息从StrictMap缓存中获取
MappedStatement ms = configuration.getMappedStatement(statement);
//执行executor对象里面的query方法
//这里的executor是在DefaultSqlSessionFactory中,
//mybatis 通过Configuration对象创建的 对应CachingExecutor
//mybatis-plus 通过MybatisConfiguration 创建MybatisCachingExecutor
//根据不同的配置,会有不同的Executor 无论那个执行器查询最终都会到下面的查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
这一次获得MappedStatement ms 如下
MybatisCachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//组装sql--我们主要研究这里的组装
BoundSql boundSql = ms.getBoundSql(parameterObject);
//获取一级缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//从缓存查询还是直接查询数据库等
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
//sqlSource 为 DynamicSqlSource(动态sql拼接)见上图
//SqlSouce里面已经解析mapper对应的sql
//已经被解析为MixedSqlNode
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
....
return boundSql;
}
<select id="selectSellReconciliationByPage"
resultType="com.jdh.trade.model.bankcontribution.resp.QuerySellReconciliationResp">
SELECT
...
WHERE sr.root_code = po.root_code
...
<if test="param.pushStatus != null and param.pushStatus !='' ">
and sr.push_status = #{param.pushStatus}
select>
@Override
public BoundSql getBoundSql(Object parameterObject) {
//下面有实体属性
DynamicContext context = new DynamicContext(configuration, parameterObject);
//处理一个个的sqlNode 编译出一个完整的xml的sql
//rootSqlNode是 MixedSqlNode 合并sql节点
//${} 已经在静态节点赋值了 因为是直接静态判断${ 然后替换 会有sql注入风险
rootSqlNode.apply(context);
//创建sql信息解析器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
//获取入参类型
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//执行解析:将带有#{}和${}的sql语句进行解析,然后封装到StaticSqlSource中
//比如and sr.push_status = #{param.pushStatus} 解析成 and sr.push_status = ?
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
//将解析后的sql语句还有入参绑定到一起(封装到一个对象中,此时还没有将参数替换到SQL占位符?)
//此时变为静态绑定 StaticSqlSource
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
将每一个节点进行各自的解析 然后拼装xml的sql 到 sqlBuilder (其实是装入string属性)
我们来看一下 rootSqlNode.apply(context);
每个节点根据自己的规则判断是否组装到sqlBuilder
MixedSqlNode.apply
这里我们注意一下为什么IfSqlNode节点会失效呢
注意:为什么param.pushStatus != ‘’ 进入这个方法会返回false
@Override
public boolean apply(DynamicContext context) {
//test = param.pushStatus != null and param.pushStatus != ''
//context.getBindings() 有前端传的参数条件
//判断符合条件就加入
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
调用ExpressionEvaluator.evaluateBoolean 进行判断
public boolean evaluateBoolean(String expression, Object parameterObject) {
//问题出现在这里 value 返回了false
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
//从这里返回boolean值 false
return (Boolean) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
return value != null;
}
再调用OgnlCache.getValue方法 获取校验结果
//表达式expression = param.pushStatus != null and param.pushStatus != ''
//root 入参
public static Object getValue(String expression, Object root) {
try {
//Mybatis底层校验使用Ognl语法
Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
//这里调用Ognl.getValue进行比较
return Ognl.getValue(parseExpression(expression), context, root);
} catch (OgnlException e) {
throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
}
}
parseExpression(expression) 会从缓存中读取Node 如果没有 去解析成特定类型的Node对象
//OgnlCache
private static Object parseExpression(String expression) throws OgnlException {
Object node = expressionCache.get(expression);
if (node == null) {
node = Ognl.parseExpression(expression);
expressionCache.put(expression, node);
}
return node;
}
比如这里转化成 ASTAND
以下节点都是继承SimpleNode
Ognl.getValue(parseExpression(expression), context, root)获取最终结果
public static Object getValue(Object tree, Map context, Object root)
throws OgnlException
{
return getValue(tree, context, root, null);
}
/**
* 计算给定的OGNL表达式树,从给定的根对象中提取值。通过addDefaultContext()为给定的上下文和根设置默认上下文。
*
* @param tree
* 要计算的OGNL表达式树,由parseExpression()返回
如:(param.pushStatus != null) && (param.pushStatus != "")
* @param context
* 求值的命名上下文
* @param root
* OGNL表达式的根对象
* @return 返回计算表达式的结果
*/
public static Object getValue(Object tree, Map context, Object root, Class resultType) throws OgnlException {
OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);
//根据解析表达式获取的节点 该例子为 ASTAND
Node node = (Node)tree;
Object result;
if (node.getAccessor() != null) {
result = node.getAccessor().get(ognlContext, root);
} else {
//调用父类SimpleNode的getValue方法
//该节点为 ASTAND
result = node.getValue(ognlContext, root);
}
...
return result;
}
调用父类 SimpleNode. getValue
以下两个方法方法为父类方法
public final Object getValue(OgnlContext context, Object source) throws OgnlException {
Object result = null;
if (context.getTraceEvaluations()) {
...
} else {
//获取常量值
result = this.evaluateGetValueBody(context, source);
}
return result;
}
调用子类自己的判断实现getValueBody 从节点树叶子节点ASTConst一层一层根据规则返回结果给上一层
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
Object result = null;
int last = this._children.length - 1;
for(int i = 0; i <= last; ++i) {
result = this._children[i].getValue(context, source);
if (i != last && !OgnlOps.booleanValue(result)) {
break;
}
}
return result;
}
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
Object v1 = this._children[0].getValue(context, source);
Object v2 = this._children[1].getValue(context, source);
return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
}
这里出现了问题为什么 0 和 ‘’ equal会相等
public static boolean equal(Object v1, Object v2) {
if (v1 == null) {
return v2 == null;
//!isEqual 判断出问题
} else if (v1 != v2 && !isEqual(v1, v2)) {
if (v1 instanceof Number && v2 instanceof Number) {
return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
} else {
return false;
}
} else {
return true;
}
}
!isEqual(v1, v2) 判断成了相等返回true
public static boolean isEqual(Object object1, Object object2) {
...
{
//前面根据Ognl的算法
result = compareWithConversion(object1, object2) == 0;
}
return result;
}
public static int compareWithConversion(Object v1, Object v2) {
...
double dv1 = doubleValue(v1);
double dv2 = doubleValue(v2);
return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);
}
public static double doubleValue(Object value) throws NumberFormatException {
if (value == null) {
return 0.0D;
} else {
Class c = value.getClass();
if (c.getSuperclass() == Number.class) {
return ((Number)value).doubleValue();
} else if (c == Boolean.class) {
return (Boolean)value ? 1.0D : 0.0D;
} else if (c == Character.class) {
return (double)(Character)value;
} else {
//这里将''改成了0.0D
String s = stringValue(value, true);
return s.length() == 0 ? 0.0D : Double.parseDouble(s);
}
}
}
最后是 “” 转换成了0.0 去比较了
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
Object result = source;
int i = 0;
for(int ilast = this._children.length - 1; i <= ilast; ++i) {
boolean handled = false;
if (i < ilast && this._children[i] instanceof ASTProperty) {
ASTProperty propertyNode = (ASTProperty)this._children[i];
int indexType = propertyNode.getIndexedPropertyType(context, result);
...
}
}
if (!handled) {
//将最后的链值返回
result = this._children[i].getValue(context, result);
}
}
return result;
}
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
//通过ASTConst 直接获取返回值 如 param 或 pushStatus 再读取入参对应的值
Object property = this.getProperty(context, source);
//根据 param 或 pushStatus 返回对应的值
Object result = OgnlRuntime.getProperty(context, source, property);
if (result == null) {
result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);
}
return result;
}
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
return this.value;
}
(1) 不用Integer接收,使用String类型接收
(2)去掉【参数!=’‘】 的非空判断
番外:如果String类型需要判断!=0,则需要写成 xxx != ‘0’.toString()