MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射. MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作.
简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具.
(它的底层是基于 JDBC 的,就像 Spring 底层是基于 Servlet 一样,都是为了让操作更简便)
分两步 :
- 添加 MyBatis 依赖
- 设置 MyBatis 配置
注意 : 这个时候项目是运行不起来的, 因为我们添加了 MyBatis 依赖, 服务器就需要知道它具体要连接哪台数据库, 以及账户密码等, 这就需要设置配置了.
# 数据库连接配置(mycnblog 对应你要操作的数据库名)
spring.datasource.url=jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径
# 在 resources/mybatis 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
# 配置 MyBatis 执行时打印 SQL(可选配置)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
先来看看数据库里有啥 :
我们就对 userinfo 进行操作 :
首先得定义 model 层的 Userinfo 类 :
@Data
public class Userinfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
再在 dao 层定义接口 :
@Mapper //数据持久层标志
public interface UserMapper {
//查询声明
//getAll 是操作数据库的方法名
List<Userinfo> getAll();
}
在 resources 下 创建 mybatis 目录(配置中定义的), 然后在该目录下定义一个 UserMapper.xml (Mapper 必须有, 配置中规定的).
查询操作 :
<?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.example.demo.dao.UserMapper">
<select id="getAll" resultType="com.example.demo.model.Userinfo">
select * from userinfo
</select>
</mapper>
select 查询操作
id=“getAll” 实现接口中的方法
resultType=“com.example.demo.model.Userinfo” 返回的数据类型
select * from userinfo 具体的 sql 语句(注意不要加分号)
在测试类中进行测试 :
@SpringBootTest //项目运行在 spring 容器中
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getAll() {
List<Userinfo> list = userMapper.getAll();
System.out.println(list);
}
}
如果类中的属性名, 与数据库表中的字段名不一致, 则查询不到结果.
@Data
public class Userinfo {
//将 id 改为iid
private int iid;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
iid 为默认的初识值 :
解决方法 :
- 将类中的属性名与数据库表中字段名保持一致
- 使用 sql 语句的 as 进行列名 (字段名) 重命名, 让列名 (字段名) 等于属性名.
<select id="getAll" resultType="com.example.demo.model.Userinfo">
select id as iid,username,password from userinfo
</select>
- 定义一个 resultMap, 将属性名和字段名进行手动映射.
type 里是要映射的类
<resultMap id="BaseMap" type="com.example.demo.model.Userinfo">
<id column="id" property="iid"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select * from userinfo
</select>
通过用户 id 查询用户信息 :
@Mapper
public interface UserMapper {
//根据 id 查找用户信息
//不能用 int 来接收对象, 因为传递的参数可能为空, int 接收会报错
Userinfo getUserById(@Param("id") Integer uid);
}
注意 :
上面 @Param 里的参数名与下面 ${} 里的参数名一致
一般来说上面的两个参数都会统一参数名, 这里为了便于说明区别没统一
下面 id 接收时, 也可以用 #{id}
<?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.example.demo.dao.UserMapper">
<select id="getUserById" resultType="com.example.demo.model.Userinfo">
select * from userinfo where id=${id}
</select>
</mapper>
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getUserById() {
Userinfo userinfo = userMapper.getUserById(1);
System.out.println(userinfo.toString());
}
}
通过上面结果可以看到使用 ${} 时, MySQL 执行如下 :
而使用 #{} 替代, 则是这样 :
${} 和 #{} 都是 MyBatis 中用来替换参数的.
${} 是直接替换, 而 #{} 是预执行.
#{} 规定了在 ? 里必须是一个参数, 不能是 SQL 命令或 SQL关键字, 而 ${} 里则可以是 SQL 命令或 SQL 语句.
这也就体现了 #{} 是安全的, ${} 是不安全的, 会有 SQL 注入问题.
使用 ${} 返回的参数, 一定得要能被穷举.
就拿用户登录来举例, 一般来说登录都是要通过用户名和密码的 :
@Mapper
public interface UserMapper {
//用户登录
Userinfo login(@Param("username")String username,
@Param("password")String password);
}
<?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.example.demo.dao.UserMapper">
<select id="login" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username=${username} and password=${password}
</select>
</mapper>
@SpringBootTest
class UserMapperTest {
@Test
void login() {
Userinfo userinfo = userMapper.login("admin","admin");
System.out.println(userinfo.toString());
}
}
可以看到这个 SQL 语句明显有问题, 正常写法得加上单引号 ‘admin’, 使用 #{} 则没有这个问题.
这个问题也可以解决, 通过手动加单引号就行 :
<select id="login" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username='${username}' and password='${password}'
</select>
这样明显更繁琐了, 而且不安全, 会有 SQL 注入风险 :
@Test
void login() {
Userinfo userinfo = userMapper.login("admin","' or 1='1");
System.out.println(userinfo.toString());
}
可以看到, 我明明没有输入正确密码, 却可以获取到用户信息, 这就是 SQL注入.
List<Userinfo> getLikeList(@Param("username") String username);
用 数据库函数 concat 进行拼接 :
<select id="getLikeList" resultType="com.example.demo.model.Userinfo">
select * from userinfo where
username like concat('%',#{username},'%')
</select>
@Test
void getLikeList() {
System.out.println(userMapper.getLikeList("管"));
}
删除、修改、添加操作默认返回的是受影响的行数
@Mapper
public interface UserMapper {
//删除操作
int delById(@Param("id") Integer id);
}
<!--这里返回类型可以省略-->
<delete id="delById">
delete from userinfo where id=#{id}
</delete>
@Mapper
public interface UserMapper {
//修改操作
int update(Userinfo userinfo);
}
<update id="update">
update userinfo set username=#{username} where id=#{id}
</update>
@Test
void update() {
Userinfo userinfo = new Userinfo();
userinfo.setId(1);
userinfo.setUsername("管理员");
int result = userMapper.update(userinfo);
System.out.println("受影响的行数 : " + result);
}
//添加操作
int add(Userinfo userinfo);
<insert id="add">
insert into userinfo(username,password,photo)
value(#{username},#{password},#{photo})
</insert>
@Test
void add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("王三");
userinfo.setPassword("123456");
userinfo.setPhoto("/image/ddd.png");
int result = userMapper.add(userinfo);
System.out.println("受影响的行数 : " + result);
}
int insert(Userinfo userinfo);
<insert id="insert" useGeneratedKeys="true"
keyColumn="id" keyProperty="id">
insert into userinfo(username,password,photo)
value(#{username},#{password},#{photo})
</insert>
keyColum : 设置数据库自增主键字段名
keyProperty : 设置数据库自增 id 赋值的属性
@Test
void insert() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("二哈");
userinfo.setPassword("123456456");
int result = userMapper.insert(userinfo);
System.out.println("受影响的行数 : " + result + ", ID : " + userinfo.getId());
}