【mybatis】IF判断的坑 (实现<if test=" status == 'zhangsan'">)

MyBatis IF标签解析问题
本文探讨了MyBatis中IF标签不执行的问题及原因,解析了MyBatis SQL生成流程,指出问题源于OGNL表达式的类型转换,并提供了解决方案。


转载  2016年11月29日 18:09:22
  • 5559

转自:http://blog.youkuaiyun.com/z69183787/article/details/51589171

最近在项目使用mybatis中碰到个问题 

Xml代码   收藏代码
  1. <if test="type=='y'">  
  2.     and status = 0   
  3. </if>  


当传入的type的值为y的时候,if判断内的sql也不会执行,抱着这个疑问就去看了mybatis是怎么解析sql的。下面我们一起来看一下mybatis 的执行过程。 

DefaultSqlSession.class  121行 
Java代码   收藏代码
  1. public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {  
  2.     try {  
  3.       MappedStatement ms = configuration.getMappedStatement(statement);  
  4.       executor.query(ms, wrapCollection(parameter), rowBounds, handler);  
  5.     } catch (Exception e) {  
  6.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
  7.     } finally {  
  8.       ErrorContext.instance().reset();  
  9.     }  
  10.   }  




在 executor.query(ms, wrapCollection(parameter), rowBounds, handler); 
执行到BaseExecutor.class执行器中的query方法 
Java代码   收藏代码
  1. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  2.     BoundSql boundSql = ms.getBoundSql(parameter);  
  3.     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);  
  4.     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);  
  5.  }  

在query的方法中看到boundSql,是通过 ms.getBoundSql(parameter);获取的。 

再点进去可以看到MappedStatement.class类中的getBoundSql方法 
Java代码   收藏代码
  1. public BoundSql getBoundSql(Object parameterObject) {  
  2.     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);  
  3.     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
  4.     if (parameterMappings == null || parameterMappings.size() <= 0) {  
  5.       boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);  
  6.     }  
  7.   
  8.     // check for nested result maps in parameter mappings (issue #30)  
  9.     for (ParameterMapping pm : boundSql.getParameterMappings()) {  
  10.       String rmId = pm.getResultMapId();  
  11.       if (rmId != null) {  
  12.         ResultMap rm = configuration.getResultMap(rmId);  
  13.         if (rm != null) {  
  14.           hasNestedResultMaps |= rm.hasNestedResultMaps();  
  15.         }  
  16.       }  
  17.     }  
  18.   
  19.     return boundSql;  
  20.   }  


看到其中有sqlSource.getBoundSql(parameterObject); sqlsource是一个接口。 
Java代码   收藏代码
  1. /** 
  2.  *  
  3.  * This bean represets the content of a mapped statement read from an XML file 
  4.  * or an annotation. It creates the SQL that will be passed to the database out 
  5.  * of the input parameter received from the user. 
  6.  *  
  7.  */  
  8. public interface SqlSource {  
  9.   
  10.   BoundSql getBoundSql(Object parameterObject);  
  11.   
  12. }  

类中getBoundSql是一个核心方法,mybatis 也是通过这个方法来为我们构建sql。BoundSql 对象其中保存了经过参数解析,以及判断解析完成sql语句。比如<if> <choose> <when> 都回在这一层完成,具体的完成方法往下看,那最常用sqlSource的实现类是DynamicSqlSource.class 


Java代码   收藏代码
  1. public class DynamicSqlSource implements SqlSource {  
  2.   
  3.   private Configuration configuration;  
  4.   private SqlNode rootSqlNode;  
  5.   
  6.   public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {  
  7.     this.configuration = configuration;  
  8.     this.rootSqlNode = rootSqlNode;  
  9.   }  
  10.   
  11.   public BoundSql getBoundSql(Object parameterObject) {  
  12.     DynamicContext context = new DynamicContext(configuration, parameterObject);  
  13.     rootSqlNode.apply(context);  
  14.     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);  
  15.     Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();  
  16.     SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());  
  17.     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);  
  18.     for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {  
  19.       boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());  
  20.     }  
  21.     return boundSql;  
  22.   }  
  23.   
  24. }  

核心方法是调用了rootSqlNode.apply(context); rootSqlNode是一个接口 
Java代码   收藏代码
  1. public interface SqlNode {  
  2.   boolean apply(DynamicContext context);  
  3. }  

可以看到类中 rootSqlNode.apply(context); 的方法执行就是一个递归的调用,通过不同的 
实现类执行不同的标签,每一次appll是完成了我们<></>一次标签中的sql创建,计算出标签中的那一段sql,mybatis通过不停的递归调用,来为我们完成了整个sql的拼接。那我们主要来看IF的实现类IfSqlNode.class 
Java代码   收藏代码
  1. public class IfSqlNode implements SqlNode {  
  2.   private ExpressionEvaluator evaluator;  
  3.   private String test;  
  4.   private SqlNode contents;  
  5.   
  6.   public IfSqlNode(SqlNode contents, String test) {  
  7.     this.test = test;  
  8.     this.contents = contents;  
  9.     this.evaluator = new ExpressionEvaluator();  
  10.   }  
  11.   
  12.   public boolean apply(DynamicContext context) {  
  13.     if (evaluator.evaluateBoolean(test, context.getBindings())) {  
  14.       contents.apply(context);  
  15.       return true;  
  16.     }  
  17.     return false;  
  18.   }  
  19.   
  20. }  


可以看到IF的实现中,执行了 if (evaluator.evaluateBoolean(test, context.getBindings()))  如果返回是false的话直接返回,否则继续递归解析IF标签以下的标签,并且返回true。那继续来看 evaluator.evaluateBoolean 的方法 

Java代码   收藏代码
  1. public class ExpressionEvaluator {  
  2.   public boolean evaluateBoolean(String expression, Object parameterObject) {  
  3.     Object value = OgnlCache.getValue(expression, parameterObject);  
  4.     if (value instanceof Boolean) return (Boolean) value;  
  5.     if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);  
  6.     return value != null;  
  7.   }  


关键点就在于这里,在OgnlCache.getValue中调用了Ognl.getValue,看到这里恍然大悟,mybatis是使用的OGNL表达式来进行解析的,在OGNL的表达式中,'y'会被解析成字符,因为java是强类型的,char 和 一个string 会导致不等。所以if标签中的sql不会被解析。具体的请参照 OGNL 表达式的语法。到这里,上面的问题终于解决了,只需要把代码修改成: 
Xml代码   收藏代码
  1. <if test='type=="y"'>  
  2.     and status = 0   
  3. </if>  

就可以执行了,这样"y"解析出来是一个字符串,两者相等!

实验02 Mybatis框架:基本配置及应用 【实验目的及要求】 1.了解Mybatis框架的基本工作原理; 2.熟悉Mybatis框架的核心对象; 3.掌握在Mybatis框架中,数据库单个表的访问方法; 4.要求所有回答的文本格式:五号,宋体、1.5倍行距,保留原来的单元格背景。 【实验步骤】 1、启动MySQL数据库服务器,执行下列脚本,创建ssmtest数据库,做好数据库访问前的准备工作。注意,sql脚本放入记事本中保存时,应采用ansi编码方式。 CREATE DATABASE ssmtest CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; use ssmtest; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- 登录日志表 DROP TABLE IF EXISTS `login_log`; CREATE TABLE `login_log` ( `log_id` int NOT NULL AUTO_INCREMENT COMMENT '日志id', `user_id` int NULL DEFAULT NULL COMMENT '用户id', `login_time` datetime NULL DEFAULT NULL COMMENT '登录时间', `login_ip` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录ip', `device_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备类型(android:安卓;ios:苹果;pc:电脑;pad:平板)', `login_result` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录结果(1:成功;0:失败)', `fail_result` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录失败原因(password_error:密码错误;sms_expire:验证码过期;account_locked:账号被锁定)', `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录地域(省市区)', PRIMARY KEY (`log_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- 插入登录数据 INSERT INTO `login_log` VALUES (1, 1, '2025-09-16 21:47:44', '192.168.10.2', 'pc', '1', NULL, '江西省宜春市袁州区'); INSERT INTO `login_log` VALUES (2, 1, '2025-09-18 23:47:23', '192.168.100.65', 'phone', '1', NULL, '江西省南昌市青山湖区'); INSERT INTO `login_log` VALUES (3, 1, '2025-09-19 21:49:12', '192.168.10.2', 'pc', '0', 'password_error', '江西省宜春市袁州区'); INSERT INTO `login_log` VALUES (4, 1, '2025-09-19 21:50:47', '192.168.10.2', 'pc', '0', 'account_locked', '江西省宜春市袁州区'); INSERT INTO `login_log` VALUES (5, 1, '2025-09-19 21:52:17', '192.168.10.2', 'pc', '1', NULL, '江西省宜春市袁州区'); -- 用户表 DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户id', `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', `full_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账户状态(1:启用;0:停用)', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`user_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- 插入用户数据 INSERT INTO `user` VALUES (1, 'admin', '123456', '管理员', '1', '2025-09-16 21:46:25'); SET FOREIGN_KEY_CHECKS = 1; 2、访问ssmtest 数据库 (1)在IntelliJ IDEA中新建一个Maven项目,取名为experiment_mybatis01,包结构为com.ycusoft.experiment。如果缺少resources目录,则自己加上去。 (2)在pom.xml文件中声明3个依赖,mybatisMyBatis框架)、mysql-connector-java(MySQL驱动)和junit(单元测试)。若想获得更多的依赖信息或采用不同的版本,可以在https://mvnrepository.com站点中,通过检索关键字获得。 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ycusoft</groupId> <artifactId>experiment_mybatis01</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <!-- 根据使用的数据库版本选择对应版本的驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies> </project> (3)右击pom.xml文件,选择Maven\Reimport菜单项,启动构件下载,将上述3个构件从远程仓库中下载至本地仓库。 (4)在resources目录下创建config文件夹,并在其下创建数据库配置文件db.properties及mybatis配置文件mybatis-config.xml。(根据红色注释补全内容)(10分) db.properties(示例,把自己的复制进来,原来的覆盖) db.driver=com.mysql.cj.jdbc.Driver # 根据数据库版本选择驱动 db.url=jdbc:mysql://localhost:3306/ssmtest?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai db.username=root db.password=yourpassword # 替换成自己的密码 Mybatis-config.xml(示例,把自己的复制进来,原来的覆盖) <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!—加载db.properties --> <!—配置开启驼峰映射,即数据库下划线字段名与Java驼峰命名映射--> <!—配置com.ycusoft.experiment.entity 包中所有类的类型别名--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </dataSource> </environment> </environments> <!—注册com.ycusoft.experiment.mapper 包中所有映射文件接口--> </configuration> (5)在src\main\java\com\ycusoft\experiment目录中,新建utils包,并在该包下新建MybatisUtil类,用于构造会话对象。代码如下: MyBatisUtil.java package com.ycusoft.experiment; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.Reader; public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory = null; static { try { Reader reader = Resources.getResourceAsReader("config/mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } } (6)在src\main\java\com\ycusoft\experiment目录中新建entity包,并创建User类和LoginLog类。代码如下:(请保持Java Bean的属性和数据表中字段间的一致): public class User { private Integer userId; private String username; private String password; private String fullName; private String status; private LocalDateTime createTime; // 省略getter/setter及toString方法 } (10分) LoginLog.java(自己实现) // 提供LoginLog类的源代码 (7)在src\main\java\com\ycusoft\experiment目录中新建mapper包,用来存放映射文件的mapper接口,并在其中创建UserMapper接口文件。 package com.ycusoft.experiment.mapper; import com.ycusoft.experiment.entity.User; import java.util.List; public interface UserMapper { /** * 查询所有用户 * @return */ List<User> selectAll(); /** * 根据id查询用户 * @param userId * @return */ User selectById(Integer userId); /** * 插入用户 * @param user * @return */ int insert(User user); /** * 根据id修改用户 * @param user * @return */ int updateById(User user); /** * 根据id删除用户 * @param userId * @return */ int deleteById(Integer userId); } (8)在src\main\resources下新建mapper目录,在其中新建UserMapper.xml文件,实现用户增删改查操作。内容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ycusoft.experiment.mapper.UserMapper"> <select id="selectAll" resultType="User"> select * from user </select> <select id="selectById" resultType="User"> select * from user where user_id=#{userId} </select> <insert id="insert" parameterType="user"> insert into user(username,password,full_name,status,create_time) values(#{username},#{password},#{fullName},#{status},#{createTime}) </insert> <update id="updateById" parameterType="User"> update user set username=#{username},password=#{password},full_name=#{fullName},status=#{status},create_time=#{createTime} where user_id=#{userId} </update> <delete id="deleteById" parameterType="int"> delete from user where user_id=#{userId} </delete> </mapper> (8)在src\test\java中,创建com.ycusoft.experiment包,并在其中新建UserTest类,在UserTest方法中编写代码,测试对User表的查询、插入、删除和更新等操作。 package com.ycusoft.experiment; import com.ycusoft.experiment.entity.User; import com.ycusoft.experiment.mapper.UserMapper; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.time.LocalDateTime; public class UserTest { @Test public void test() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 查询所有用户并打印 userMapper.selectAll().forEach(System.out::println); // 插入用户 User user = new User(); user.setUsername("zhangsan"); user.setPassword("123456"); user.setFullName("张三"); user.setStatus("1"); user.setCreateTime(LocalDateTime.now()); System.out.println("开始插入用户..."); int result = userMapper.insert(user); sqlSession.commit(); System.out.println("插入用户成功!"); // 查询所有用户并打印 userMapper.selectAll().forEach(System.out::println); // 更新管理员用户 User adminUser = userMapper.selectById(1); adminUser.setFullName("修改后的管理员"); adminUser.setPassword("88888888"); System.out.println("开始更新管理员用户..."); result = userMapper.updateById(adminUser); sqlSession.commit(); System.out.println(userMapper.selectById(1)); // 删除刚刚新增的用户 System.out.println("开始删除用户..."); result = userMapper.deleteById(2); sqlSession.commit(); // 查询所有用户并打印 userMapper.selectAll().forEach(System.out::println); } } 此时会遇到” org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ycusoft.experiment.mapper.UserMapper.selectAll”报错,请通过网上资料或AI查找报错原因,并在此简述报错的原因以及解决方法:(10分) // 简述报错的原因以及解决方法 修复完报错后,在下方粘贴控制台运行结果截图: (12)继续完成数据表login_log的增删改查(要求采用接口方式)。除上述示例的增删改查方法外,再实现两个方法:(60分) ①列出近五天的登录用户数量; ②统计登录失败的用户数。 (可以自己往数据表里多插入一些数据,也可以让AI生成插入数据的SQL) LoginLogMapper.java // 提供LoginLogMapper接口的源代码 LoginLogMapper.xml // 提供LoginLogMapper映射文件的源代码 LoginLogTest.java // 提供LoginLog单元测试的源代码 提供控制台运行截图:(10分) 这个实验我具体需要怎么做
09-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值