一、关于tkMybatis
  Tkmybatis 是基于 Mybatis 框架开发的一个工具,通过调用它提供的方法实现对单表的数据操作,不需要写任何 sql 语句,极大地提高了项目开发效率。
1.1 相关依赖
xml
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>版本号</version>
</dependency>
1.2 使用方法
引入tk.mybatis的依赖后。定义数据库表映射类:
java
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapepr继承其提供的接口,常见的有:
- BaseMapper
- MySqlMapper
- IdsMapper
- ConditionMapper
- ExampleMapper
这里以BaseMapper为例:
```Java
import tk.mybatis.mapper.common.BaseMapper;
public interface UserMapper extends BaseMapper
}
```
然后就可以调用接口中封装好的方法进行SQL操作了,例如通过selectAll查询所有的user信息:
```Java
@Resource
private UserMapper userMapper;
@RequestMapping(value = "/getUserInfo")
public List<User> getUserInfo(){
List<User> result =userMapper.selectAll();
return result;
}
```
对应的方法实现原理也很简单,主要是通过mybatis的Provider注解来实现 的:
```java
public interface SelectAllMapper
/*
* 查询全部结果
*
* @return
/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List
}
```
```java
/*
* 查询全部结果
*
* @param ms
* @return
/
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
// 逻辑删除的未删除查询条件
sql.append("<where>");
sql.append(SqlHelper.whereLogicDelete(entityClass, false));
sql.append("</where>");
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}
```
因为Provider其实只需要返回一个 SQL 字符串 ,所以tkMybatis在防护 SQL 注入时,也是对对应的内容,通过#{}的格式来进行预编译的 ,例如其中的一个拼接method:
java
/**
* 返回格式如:#{entityName.age+suffix,jdbcType=NUMERIC,typeHandler=MyTypeHandler}+separator
*
* @param entityName
* @param suffix
* @param separator
* @return
*/
public String getColumnHolder(String entityName, String suffix, String separator) {
StringBuffer sb = new StringBuffer("#{");
if (StringUtil.isNotEmpty(entityName)) {
sb.append(entityName);
sb.append(".");
}
sb.append(this.property);
if (StringUtil.isNotEmpty(suffix)) {
sb.append(suffix);
}
//如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的
if (this.jdbcType != null) {
sb.append(", jdbcType=");
sb.append(this.jdbcType.toString());
}
//为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类,例如枚举
if (this.typeHandler != null) {
sb.append(", typeHandler=");
sb.append(this.typeHandler.getCanonicalName());
}
//3.4.0 以前的 mybatis 无法获取父类中泛型的 javaType,所以如果使用低版本,就需要设置 useJavaType = true
//useJavaType 默认 false,没有 javaType 限制时,对 ByPrimaryKey 方法的参数校验就放宽了,会自动转型
if (useJavaType && !this.javaType.isArray()) {//当类型为数组时,不设置javaType#103
sb.append(", javaType=");
sb.append(javaType.getCanonicalName());
}
sb.append("}");
if (StringUtil.isNotEmpty(separator)) {
sb.append(separator);
}
return sb.toString();
}
同时也支持Myabtis的xml和注解的配置方式。此外还提供了扩展机制,通过 Example 和 Criteria 类进行动态SQL 的拼装。
二、常见SQL注入场景
2.1 Example
通过提供的Example可以添加查询条件,相当于是sql语句中的where后面的条件部分。例如如下的例子:
mapper继承tk.mybatis.mapper.common.Mapper(包含了tk.mybatis.mapper.common.ExampleMapper):
```java
public interface UserMapper extends Mapper
}
```
在service层,通过Example生成对应的动态sql并封装在方法里:
```java
@Service
public class UserServiceImpl implements IUserService{
@Autowired
UserMapper userMapper;
@Override
public List<User> findByUserName(String username) {
// TODO Auto-generated method stub
Example example = new Example(User.class);
Example.Criteria criteria = example.createCriteria();
criteria.andLike("name", username);
List<User> userList = userMapper.selectByExample(example);
return userList;
}
}
```
Controller里调用service的方法:
```java
@Autowired
IUserService userService;
@RequestMapping(value = "/getUserInfoByUserName")
public List<User> getUserInfoByUserName(String username){
List<User> result =userService.findByUserName(username);
return result;
}
```
直接访问接口即可进行模糊查询:
同时也进行了预编译处理:
跟所有的SQL注入一样,动态表名&Order by查询没办法进行预编译处理,会存在SQL拼接。Example 和 Criteria也提供了相关的method。
2.1.1 order by排序
可以通过example.setOrderByClause()方法来进行排序 。这里若前端可控且没有进行相关的安全检查会存在SQL注入风险:
java
@Override
public List<User> sortBy(String sort) {
// TODO Auto-generated method stub
Example example = new Example(User.class);
example.setOrderByClause(sort);
List<User> userList = userMapper.selectByExample(example);
return userList;
}
尝试写入1/0,成功触发SQL error,存在注入风险:
对于这种情况,除了对输入参数进行安全检查,还可以通过Example.builder方式进行查询,其会将查询内容与entity进行绑定,在一定程度上规避了注入的风险:
java
Example example = Example.builder(User.class)
.orderByDesc(sort)
.build();
或者使用example.orderBy()方法,同样将查询内容与entity进行绑定:
java
private String property(String property) {
if (StringUtil.isEmpty(property) || StringUtil.isEmpty(property.trim())) {
throw new MapperException("接收的property为空!");
}
property = property.trim();
if (!propertyMap.containsKey(property)) {
throw new MapperException("当前实体类不包含名为" + property + "的属性!");
}
return propertyMap.get(property).getColumn();
}
2.1.2 动态表名
可以通过以下方式实现动态表名。
首先在实体Dao添加非表字段参数,用于接受动态表名参数:
java
@Transient
private String tableName;
然后实现接口IDynamicTableName,并实现getDynamicTableName()方法:
```java
@Data
public class User implements IDynamicTableName{
@Transient
private String tableName;
public Long getId() {
return id;
}
public String getDynamicTableName() {
......
}
```
然后通过example.setTableName()即可设置查询的动态表名:
```java
@Autowired
UserMapper userMapper;
@Override
public List<User> DynamicTableName(String tableName) {
// TODO Auto-generated method stub
Example example = new Example(User.class);
example.setTableName(tableName);
example.setOrderByClause("id desc");
List<User> userList = userMapper.selectByExample(example);
return userList;
}
```
例如查询user表的内容:
因为这里是动态拼接的,实际上存在SQL注入风险:
可以看到user表后的内容where 1=1/0成功拼接到SQL中并执行:
2.2 Condition
Condition条件查询,其继承了Example:
```java
public class Condition extends Example {
public Condition(Class<?> entityClass) {
super(entityClass);
}
public Condition(Class<?> entityClass, boolean exists) {
super(entityClass, exists);
}
public Condition(Class<?> entityClass, boolean exists, boolean notNull) {
super(entityClass, exists, notNull);
}
}
```
也就说说动态表名跟order by排序的场景也是存在的:
java
condition.setOrderByClause();
condition.setTableName();
2.3 criteria动态SQL拼接
Criteria是Example中的一个内部类,在最终sql构建时以括号呈现,Criteria里带了较多构建查询条件的方法,方便生成动态SQL。
java
Example example = new Example(CcompareccicModel.class);
Criteria criteria = example.createCriteria();
criteria的如下方法也是直接进行SQL拼接的:
java
criteria.andCondition(String condition)
criteria.andCondition(String condition,Object value)
criteria.orCondition(String condition)
criteria.orCondition(String condition,Object value)
例如andCondition方法实际上是增加了动态查询的条件:
```java
/*
* 手写条件
*
* @param condition 例如 "length(countryname)<5"
* @return
/
public Criteria andCondition(String condition) {
addCriterion(condition);
return (Criteria) this;
}
/**
* 手写左边条件,右边用value值
*
* @param condition 例如 "length(countryname)="
* @param value 例如 5
* @return
*/
public Criteria andCondition(String condition, Object value) {
criteria.add(new Criterion(condition, value));
return (Criteria) this;
}
```
2.4 注解&xml配置
因为tkmybatis同时也支持Myabtis的xml和注解的配置方式,所以跟mybatis一样,关注${}格式的动态SQL即可。
三、参考资料
https://github.com/abel533/Mapper
相关推荐: Code Security Guide -Thinkphp3.2开发
一、Thinkphp安装基础 1.1安装ThinkPHP Thinkphp ->MVC结构 ->model(数据库) ->view(显示) ->comtrol(逻辑结构控制) Thinkphp 版本:3.2.3,解开压缩包之后,准备好我…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论