资料
nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 2020-01-02 19:01:21 [WARN] ExceptionHandlerExceptionResolver:194 - Resolved [org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘is_disabled’ in ‘class com.lyloou.flow.model.flow.Flow’]
1 2 3 4 5 6 7 8 9 @Update("insert into flow (day,item,is_disabled,is_archived) values (#{day},#{item},#{is_disabled},#{is_archived}) on duplicate key update" + " item=values(item),is_disabled=values(is_disabled),is_archived=values(is_archived)") int insertOrUpdateFlow (Flow flow) ;@Update("insert into flow (day,item,is_disabled,is_archived) values (#{day},#{item},#{isDisabled},#{isArchived}) on duplicate key update" + " item=values(item),is_disabled=values(is_disabled),is_archived=values(is_archived)") int insertOrUpdateFlow (Flow flow) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Data @Builder @AllArgsConstructor @NoArgsConstructor public class Flow { private Long id; private Date gmtCreate; private Date gmtModified; private String day; private String item; private Boolean isArchived; private Boolean isDisabled; }
1 2 3 2020-01-02 19:01:21 [WARN] ExceptionHandlerExceptionResolver:194 - Resolved [org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'is_disabled' in 'class com.lyloou.flow.model.flow.Flow'] 答案:因为解决的时候用到的是 `flow.isDisabled` 属性,因为传递的是 `flow.is_ disabled` 所以不行
按照输入 id 顺序来返回数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="queryOrderedAuthorByauthorIds" resultMap ="BaseResultMap" > select * from video_author a where a.user_id in <foreach collection ="authorIds" item ="item" index ="index" separator ="," open ="(" close =")" > #{item} </foreach > and a.user_status=0 -- 按照输入的顺序来返回排序 order by field(a.user_id, <foreach collection ="authorIds" item ="item" index ="index" separator ="," > #{item} </foreach > ) </select >
自动填充字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Data @Accessors(chain = true) public class Person implements Serializable { private static final long serialVersionUID = 1L ; @TableId(type = IdType.AUTO) @ApiModelProperty(value = "实体ID") private Integer id; @ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date createdTime; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date updatedTime; @ApiModelProperty(value = "创建人") @TableField(value = "creator", fill = FieldFill.INSERT) private String creator; @ApiModelProperty(value = "修改人") @TableField(value = "modifier", fill = FieldFill.INSERT_UPDATE) private String modifier; } @Component public class MybatisObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { Integer userId = AuthenticationService.getUserId(); if (userId != null ) { Stream.of("createdBy" , "updatedBy" , "modifier" , "creator" ) .forEach(s -> setFieldValIfNull(s, userId.toString(), metaObject)); } Stream.of("createdTime" , "updatedTime" ) .forEach(s -> setFieldValIfNull(s, new Date (), metaObject)); } private void setFieldValIfNull (String field, Object fieldVal, MetaObject metaObject) { final Object value = getFieldValByName(field, metaObject); if (value != null ) { setFieldValByName(field, fieldVal, metaObject); } } @Override public void updateFill (MetaObject metaObject) { Stream.of("updatedTime" ) .forEach(s -> setFieldValIfNull(s, new Date (), metaObject)); setFieldValByName("updatedTime" , new Date (), metaObject); Integer userId = AuthenticationService.getUserId(); if (userId != null ) { Stream.of("updatedBy" , "modifier" ) .forEach(s -> setFieldValIfNull(s, userId.toString(), metaObject)); } } }
mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下:
1 select * from user where name = "ruhua";
上述 sql 中,我们希望 name 后的参数 “ruhua” 是动态可变的,即不同的时刻根据不同的姓名来查询用户。在 sqlMap 的 xml 文件中使用如下的 sql 可以实现动态传递参数 name:
1 select * from user where name = #{name };
或者
1 select * from user where name = '${name}' ;
对于上述这种查询情况来说,使用 #{ } 和 ${ } 的结果是相同的,但是在某些情况下,我们只能使用二者其一。
‘#’ 与 ‘$’ 区别 动态 SQL 是 mybatis 的强大特性之一,也是它优于其他 ORM 框架的一个重要原因。mybatis 在对 sql 语句进行预编译之前,会对 sql 进行动态解析,解析为一个 BoundSql 对象,也是在此处对动态 SQL 进行处理的。
在动态 SQL 解析阶段, #{ } 和 ${ } 会有不同的表现:
#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。
例如,sqlMap 中如下的 sql 语句
1 select * from user where name = #{name };
解析为:
1 select * from user where name = ?;
一个 #{ } 被解析为一个参数占位符 ?
。
而,
${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换
例如,sqlMap 中如下的 sql
1 select * from user where name = '${name}' ;
当我们传递的参数为 “ruhua” 时,上述 sql 的解析为:
1 select * from user where name = "ruhua";
预编译之前的 SQL 语句已经不包含变量 name 了。
综上所得, ${ } 的变量的替换阶段是在动态 SQL 解析阶段,而 #{ }的变量的替换是在 DBMS 中。
用法 tips
1、能使用 #{ } 的地方就用 #{ }
首先这是为了性能考虑的,相同的预编译 sql 可以重复利用。
其次,**${ } 在预编译之前已经被变量替换了,这会存在 sql 注入问题**。例如,如下的 sql,
1 select * from ${tableName} where name = #{name }
假如,我们的参数 tableName 为 user; delete user; --
,那么 SQL 动态解析阶段之后,预编译之前的 sql 将变为
1 select * from user ; delete user ;
--
之后的语句将作为注释,不起作用,因此本来的一条查询语句偷偷的包含了一个删除表数据的 SQL!
2、表名作为变量时,必须使用 ${ }
这是因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 ''
,这会导致 sql 语法错误,例如:
1 select * from #{tableName} where name = #{name };
预编译之后的 sql 变为:
1 select * from ? where name = ?;
假设我们传入的参数为 tableName = “user” , name = “ruhua”,那么在占位符进行变量替换后,sql 语句变为
1 select * from 'user' where name ='ruhua' ;
上述 sql 语句是存在语法错误的,表名不能加单引号 ''
(注意,反引号 ``是可以的)。
sql 预编译 定义
sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译。
为什么需要预编译 JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译
预编译阶段可以优化 sql 的执行 。 预编译之后的 sql 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的 sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。
预编译语句对象可以重复利用 。 把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个 sql,可以直接使用这个缓存的 PreparedState 对象。
mybatis 默认情况下,将对所有的 sql 进行预编译。
mysql 预编译源码解析 mysql 的预编译源码在 com.mysql.jdbc.ConnectionImpl
类中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public synchronized java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { checkClosed(); PreparedStatement pStmt = null ; boolean canServerPrepare = true ; String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; if (this .useServerPreparedStmts && getEmulateUnsupportedPstmts()) { canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); } if (this .useServerPreparedStmts && canServerPrepare) { if (this .getCachePreparedStatements()) { synchronized (this .serverSideStatementCache) { pStmt = (com.mysql.jdbc.ServerPreparedStatement)this .serverSideStatementCache.remove(sql); if (pStmt != null ) { ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false ); pStmt.clearParameters(); } if (pStmt == null ) { try { pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this .database, resultSetType, resultSetConcurrency); if (sql.length() < getPreparedStatementCacheSqlLimit()) { ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true ; } pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { if (getEmulateUnsupportedPstmts()) { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false ); if (sql.length() < getPreparedStatementCacheSqlLimit()) { this .serverSideStatementCheckCache.put(sql, Boolean .FALSE); } } else { throw sqlEx; } } } } } else { try { pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this .database, resultSetType, resultSetConcurrency); pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { if (getEmulateUnsupportedPstmts()) { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false ); } else { throw sqlEx; } } } } else { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false ); } return pStmt; }
流程图如下所示:
mybatis 之 sql 动态解析以及预编译源码 mybatis sql 动态解析 mybatis 在调用 connection 进行 sql 预编译之前,会对 sql 语句进行动态解析,动态解析主要包含如下的功能:
mybatis 强大的动态 SQL 功能的具体实现就在此。动态解析涉及的东西太多,以后再讨论。
批量处理示例(修复歌手名称) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public void fixSongSingleName () { int id = 0 ; int limit = 100 ; List<CcMusicInfoEntity> list; do { list = ccMusicInfoMapper.queryListByLastIdWithLimit(id, limit); if (CollUtil.isEmpty(list)) { break ; } id = list.get(list.size() - 1 ).getId(); final List<CcMusicInfoEntity> needUpdateList = list.stream() .filter(Objects::nonNull) .filter(it -> SingerNameUtil.isContainCombinedSeparator(it.getSingerName())) .collect(Collectors.toList()); if (CollUtil.isNotEmpty(needUpdateList)) { final List<String> originSingerNameList = needUpdateList.stream().map(CcMusicInfoEntity::getSingerName).collect(Collectors.toList()); updateSongSingerInfo(needUpdateList); Map<Integer, String> map = new HashMap <>(needUpdateList.size()); for (int i = 0 ; i < needUpdateList.size(); i++) { final CcMusicInfoEntity entity = needUpdateList.get(i); final String originSingerName = originSingerNameList.get(i); map.put(entity.getId(), StrUtil.format("更新前:{},更新后:{}" , originSingerName, entity.getSingerName())); } logger.info(StrUtil.format("fixSongSingleName 本次更新数量,{},修改数据:{}" , needUpdateList.size(), map)); } logger.info("fixSongSingleName: 本次更新到ID," + id); } while (CollUtil.isNotEmpty(list)); logger.info("fixSongSingleName: 本次更新完成" ); } private void updateSongSingerInfo (List<MusicInfoEntity> needUpdateList) { List<MusicInfoEntity> list = new ArrayList <>(needUpdateList.size()); for (MusicInfoEntity entity : needUpdateList) { final String originSingerName = entity.getSingerName(); final String calculateSingerName = SingerNameUtil.calculate(originSingerName); if (Objects.equals(originSingerName, calculateSingerName)) { continue ; } MusicInfoEntity newCcMusicInfoEntity = new MusicInfoEntity (); newCcMusicInfoEntity.setId(entity.getId()); newCcMusicInfoEntity.setLastUpdateDate(new Date ()); newCcMusicInfoEntity.setSingerName(calculateSingerName); list.add(newCcMusicInfoEntity); } if (CollUtil.isNotEmpty(list)) { musicInfoMapper.updateByList(list); } }
批量查询:
1 2 3 List<MusicInfoEntity> queryListByLastIdWithLimit (@Param("id") int id, @Param("limit") int limit) ; void updateByList (List<MusicInfoEntity> list) ;
1 2 3 4 5 6 7 <select id ="queryListByLastIdWithLimit" resultMap ="BaseResultMap" > SELECT <include refid ="Base_Column_List" /> FROM t_music_info a where a.id > #{id} limit #{limit} </select >
批量插入:
mysql 一条语句 update 多条记录_chijiaodaxie 的博客-CSDN 博客_mysql update 多条数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <update id ="updateByList" parameterType ="object" > update music_info <trim prefix ="set" suffixOverrides ="," > <trim prefix ="singer_name=case" suffix ="end," > <foreach collection ="list" item ="item" index ="index" > WHEN id=#{item.id} THEN #{item.singerName} </foreach > </trim > <trim prefix ="last_update_date=case" suffix ="end," > <foreach collection ="list" item ="item" index ="index" > WHEN id=#{item.id} THEN #{item.lastUpdateDate} </foreach > </trim > </trim > WHERE id IN <foreach collection ="list" item ="item" open ="(" close =")" separator ="," > #{item.id} </foreach > </update >
总结 本文主要深入探究了 mybatis 对 #{ } 和 ${ }的不同处理方式,并了解了 sql 预编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 第一种写法(1): 原符号 < <= > >= & ' " 替换符号 < 例如:sql如下: create_date_time > 第二种写法(2): 大于等于 <![CDATA[ >= ]] > 小于等于 <![CDATA[ <= ]] > 例如:sql如下: create_date_time <![CDATA[ >= ]] >