首先我们来解释下MyBaits的中xml文件的常见参数
parameterType:入参类型,一般我们写全类名包装类,如java.lang.String,减少不必要的错误。
如果是POJO对象的话,我们需要从包名开始写。
resultType:返回值类型,规则和入参基本一致。
resultMap:返回映射,当sql查询字段名和pojo的属性名不一致使用,可以通过resultMap将字段名和属性名作一个对应关系 。
接下来我们来看一下MyBatis是如何获取用户传的参数的。首先抛出结论。
Mybatis获取参数值得两种方式:${}和#{},${}的本质是字符串拼接,驱动在向数据库发送sql语句时便已经将sql语句拼接完整,这种方式不安全,会导致sql注入危机;而#{}的本质是占位符赋值,采用预编译的方式,在驱动向数据库发送sql语句时仅提供占位符,在mybatis运行时占位符才会被参数取代,这种方式很安全且大大提高了运行的效率。
下面我们来实测验证,首先我们在主配置文件添加sql输出日志
<settings>
<!--设置mybatis输出日志-->
<!--logImpl:表示对日志的控制-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
id username birthday sex address
14 wkx 2022-04-28 男 保定 这是数据库里的一条记录
我们在目标xml文件中写一条以username为条件的查询语句,请注意我们这里用#{}传参数
<select id="selectByUsername" resultType="com.qcby.entity.User" parameterType="java.lang.String">
select * from user where username = #{username}
</select>
我们直接看控制台查询结果
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 289639718.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@11438d26]
==> Preparing: select * from user where username = ?
==> Parameters: wkx(String)
<== Columns: id, username, birthday, sex, address
<== Row: 14, wkx, 2022-04-28 16:19:02.0, 男, 保定
<== Total: 1
User{id=14, username='wkx', birthday=Thu Apr 28 16:19:02 CST 2022, sex='男', address='保定'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@11438d26]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@11438d26]
Returned connection 289639718 to pool.
我们可以看到打印的sql语句传的参数是一个“?”,且查询成功为我们返回了一条记录。
接下来我们使用${}来传参
<select id="selectByUsername" resultType="com.qcby.entity.User" parameterType="java.lang.String">
select * from user where username = ${value}
</select>
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 243745864.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
==> Preparing: select * from user where username = wkx
==> Parameters:
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
Returned connection 243745864 to pool.
我们可以看到sql语句直接将我们传的参数打印了出来,但是并没有查询成功,原因是我们查询的是字符串,但是传参时并没有帮我们添加引号。
从上述案例我们便可得到一个结论,#{}传参是占位符,${}传参是字符串的拼接。所以如果我们使用${}传参想要查询成功的话,我们需要手动添加引号,而占位符会自动识别我们传参的类型。
select * from user where username = '${value}'
添加引号之后便可查询成功。
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 243745864.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
==> Preparing: select * from user where username = 'wkx'
==> Parameters:
<== Columns: id, username, birthday, sex, address
<== Row: 14, wkx, 2022-04-28 16:19:02.0, 男, 保定
<== Total: 1
User{id=14, username='wkx', birthday=Thu Apr 28 16:19:02 CST 2022, sex='男', address='保定'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448]
Returned connection 243745864 to pool.
接下来我们测试一下${}传参的不安全行为----sql注入
select * from user where username = ${username}; 假设我们的目标任务是查询条件是username为“wkx”的记录信息,那么在我们传入其他参数时程序理应报错。
我们首先传入一个正确的参数。
public void search(){
User user = mapper.selectByUsername("wkx");
System.out.println(user);
}
没有任何问题,可以查到我们想要的数据。
我们来传入这样一个参数:
public void search(){
User user = mapper.selectByUsername("'wkx' or username = '更新'");
System.out.println(user);
}
我们可以看到查询到了两条记录。如果后面拼接的是一条删除语句的话,整个数据库的数据都会有危险。这就是sql注入。
当我们使用#{}传参时便不会有这种烦恼。我们可以看到并没有查询到任何信息。
那么我们应该如何选择使用这两种参数传递的方式那。
1.能用#{}就不用${}
2.传入数据是表的字段,如order by 字段排序,这个时候可以用${}
3.在使用${}时,可以使用@param("")给参数重命名
4.模糊查询时也可使用${}