1、mybatis简单例子
MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数库中的记录.
mybatis和hibernate区别:mybatis是用sql语句生成对象,hibernate是以对象生成sql语句。
1、添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、添加mybatis.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/userMapper.xml"/>
</mappers>
</configuration>
3、实体类
@Data
public class User {
private int id;
private String name;
private int age;
//get,set方法
}
4、定义userMapper接口
public interface UserMapper {
User getUser(int i);
}
5、定义操作数据库的sql映射文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.webservice.webserver.mapper.UserMapper">
<select id="getUser" parameterType="int" resultType="com.webservice.webserver.model.User">
SELECT *
FROM users where id =#{id}
</select>
</mapper>
6、测试文件
@Test
public void test() throws IOException {
String resource="mybatis.xml";
//读取配置文件
Reader reader= Resources.getResourceAsReader(resource);
//获取会哈工厂
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession=sqlSessionFactory.openSession();
//查询
String sql="com.webservice.webserver.mapper.UserMapper.getUser";
//调用API查询
User user=sqlSession.selectOne(sql,1);
System.out.println(user.toString());
}
输出正确信息即为正确。
2、mybatisSQL注入
直接上例子比较直观。
1、创建表和测试数据
create table user_table(
id int Primary key,
username varchar(30),
password varchar(30)
);
insert into user_table values(1,'test-1','12345');
insert into user_table values(2,'test-2','12345');
2、jdbc进行加载模拟一个登录过程
@Test
public void test2() {
try {
String username = "test-1";
String password = "12345";
String sql = "SELECT id,username FROM user_table WHERE " + "username='" + username + "'AND " + "password='"
+ password + "'";
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
PreparedStatement stat = con.prepareStatement(sql);
System.out.println(stat.toString());
ResultSet rs = stat.executeQuery();
while (rs.next()) {
String id = rs.getString(1);
String name = rs.getString(2);
System.out.println("id:" + id + "---name:" + name);
}
} catch (Exception e){
e.printStackTrace();
}
}
可以查到test-1用户的信息。
3、如果test-1的密码为1234567呢?很明显是查不到的。但如果是用户名或者密码为' or 1='1(这里不一定是这个值,只要保证恒等就可)。神奇的事情发生了,查到了????
因此有的人就说这样设置传入SQL的参数。
String sql = "SELECT id,username FROM user_table WHERE username=? AND password=?";
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
PreparedStatement stat = con.prepareStatement(sql);
stat.setString(1,username);
stat.setString(2,password);
这样为什么能呢?
看看里面真正执行的SQL
SELECT id,username FROM user_table WHERE username='test-1' AND password='1234556 \' or 1=\'1'
是不是恍然大悟?
但是如果将传入的参数改为包含SQL注解的呢?比如
String username = "' or 1='1 -- test-1";
String username = " ' or 1='1';";
预编译也能解决,只是提一下这种情况。。。
2.1 mybatis $和#
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。
在下面的语句中,如果 username 的值为 zhangsan,则两种方式无任何区别:
select * from user where name = #{name};
select * from user where name = ${name};
其解析之后的结果均为
select * from user where name = 'zhangsan';
但是 #{} 和 ${} 在预编译中的处理是不一样的。#{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:
select * from user where name = ?;
而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成
select * from user where name = 'zhangsan';
以上,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。
那么,在使用过程中我们应该使用哪种方式呢?
答案是,优先使用 #{}。因为 ${} 会导致 sql 注入的问题。看下面的例子:
select * from ${tableName} where name = #{name}
在这个例子中,如果表名为
user; delete user; --
则动态解析之后 sql 如下:
select * from user; delete user; -- where name = ?;
--之后的语句被注释掉,而原本查询用户的语句变成了查询所有用户信息+删除用户表的语句,会对数据库造成重大损伤,极大可能导致服务器宕机。
但是表名用参数传递进来的时候,只能使用 ${} ,具体原因可以自己做个猜测,去验证。这也提醒我们在这种用法中要小心sql注入的问题。
例子:
在简单例子和前面注入的基础上。
添加UserTable类
@Data
public class UserTable {
private int id;
private String userName;
private String passWord;
}
UserTableMapper.java
public interface UserTableMapper {
UserTable login(UserTable userTable);
}
userTableMapper.xml记得引入到mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.webservice.webserver.mapper.UserTableMapper">
<select id="login" parameterType="com.webservice.webserver.model.UserTable"
resultType="com.webservice.webserver.model.UserTable">
SELECT id ,username as userName FROM user_table WHERE
username=${userName} AND password=${passWord}
</select>
</mapper>
test:
@Test
public void test3(){
try{
String resource = "mybatis.xml";
// 读取配置文件
Reader reader = Resources.getResourceAsReader(resource);
// 获取会话工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession openSession = sqlSessionFactory.openSession();
// 查询
String sql = "com.webservice.webserver.mapper.UserTableMapper.login";
// 调用api查询
UserTable userTable = new UserTable();
userTable.setUserName("'' OR 1=1 -- ");
userTable.setPassWord("12345");
List<UserTable> listUserTable = openSession.selectList(sql, userTable);
for (UserTable ub : listUserTable) {
System.out.println(ub.getUserName());
}
} catch (Exception e){
e.printStackTrace();
}
}
使用${}后执行以上测试程序居然能得到结果??开启日志调试后得到SQL执行语句:
14:10:55.328 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - ==> Preparing: SELECT id ,username as userName FROM user_table WHERE username='' OR 1=1 -- AND password=12345
14:10:55.354 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - ==> Parameters:
14:10:55.373 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - <== Total: 2
可以看到${}仅仅是简单的字符串替换罢了,很容易就被SQL注入了。
如果使用#{}呢?
14:16:49.407 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - ==> Preparing: SELECT id ,username as userName FROM user_table WHERE username=? AND password=?
14:16:49.434 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - ==> Parameters: '' OR 1=1 -- (String), 12345(String)
14:16:49.449 [main] DEBUG com.webservice.webserver.mapper.UserTableMapper.login - <== Total: 0
其实就是预编译了而已和jdbc加载的没什么区别。