Mybatis(之前称为 iBatis)也是一个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。
ORM 把数据库映射为对象:
create database mycnblog default character set utf8mb4;
use mycnblog;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default now(),
updatetime datetime default now(),
`state` int default 1
) default charset 'utf8mb4';
insert into userinfo values(1, 'admin', '123', '', '2023-12-10 10:09:30', '2023-12-10 10:09:30', 1);
MyBatis Framework 和 MySQL Driver
引入之后,我们的项目是启动不了的,还需要进行配置。
配置 application.yml,指定要连接的数据库在哪
spring:
datasource:
url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
如果 mysql-connector-java 是5.x 之前的使用的是 com.mysql.jdbc.Driver,之后的使用的是 com.mysql.cj.jdbc.Driver
指定 MyBatis 在哪里查找映射器(Mapper)的 XML 文件:
在 MyBatis 中,Mapper 文件定义了 SQL 映射和与数据库的交互。
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
classpath:mapper/**Mapper.xml
:表示在类路径(classpath)下的 mapper
目录中查找所有以 Mapper.xml
结尾的文件。
** 是通配符,它和 * 的区别是:
**
包括子目录,可以递归匹配多级子目录。*
不包括子目录,只匹配当前目录下的文件。如果想看 MyBatis 最终执行的 SQL 语句,还可以进行如下配置:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
完成上述操作,启动项目,如果可以启动,就说明我们的环境配置完成了。
MyBatis 主要由两部分组成:
这个类的属性名和数据库表中的字段名一致
package org.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private Date createtime;
private Date updatetime;
private int state;
}
@Mapper
注解是 MyBatis 框架中用于标识一个接口为 MyBatis 映射器(Mapper)的注解。
当你在一个接口上使用 @Mapper
注解时,MyBatis 将会扫描这个接口,并为其创建一个实现类,用于执行与数据库相关的 SQL 操作。
Spring 将会在运行时为这个接口生成代理对象,使其可以被注入到其他组件中
package org.example.mybatisdemo.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.mybatisdemo.model.UserInfo;
import java.util.List;
@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
List<UserInfo> getAll();
}
xml 文件中复制如下代码(固定格式):
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>
namespace
里填要实现的接口的路径。
例:mapper 里面添加 select 标签,表示要实现查询方法,id 指定实现的方法名(和接口中的一致),result 指定返回的记录的类型,标签内部填写具体的 sql 语句
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatisdemo.mapper.UserMapper">
<select id="getAll" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo
select>
mapper>
在要测试的代码上右键,选择 Generate->Test
生成的测试类会进入到 test 目录下面
设置当前测试环境为 SpringBoot,并注入要测试的类:
package org.example.mybatisdemo.mapper;
import org.example.mybatisdemo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest // 当前测试的上下文环境为SpringBoot
class UserMapperTest {
@Autowired // 注入要测试的类
private UserMapper userMapper;
@Test // 测试
void getAll() {
List<UserInfo> list = userMapper.getAll();
for (UserInfo user : list) {
System.out.println(user.toString());
}
}
}
运行测试方法,查看测试结果:
UserInfo(id=1, username=admin, password=123, photo=, createtime=Sun Dec 10 10:09:30 HKT 2023, updatetime=Sun Dec 10 10:09:30 HKT 2023, state=1)
查询单条记录需要提供参数,供 where 语句使用。
在抽象方法的参数列表中,用 @Param
注解指定传递给 SQL 语句的参数名
@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
List<UserInfo> getAll();
UserInfo getUserById(@Param("id") Integer id);
}
在 XML 映射文件中,可以使用 #{id}
来引用方法参数。
<select id="getUserById" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo where id = #{id}
select>
注:@Param("id")
也可以省略,通常建议写上。
测试:
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo);
}
@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
List<UserInfo> getAll();
UserInfo getUserById(@Param("id") Integer id);
int add(UserInfo userInfo); // 传入的是对象,不加 @Param 注解
}
xml 文件中使用 insert 标签,直接用 #{属性名} 来获取对象中的属性:
<insert id="add">
insert into userinfo(username, password)
values (#{username}, #{password})
insert>
如果传入对象的时候加了 @Param 注解,如:
int add(@Param("userInfo") UserInfo userInfo);
那么 xml 获取属性的时候,必须要使用 .
<insert id="add">
insert into userinfo(username, password)
values (#{userInfo.username}, #{userInfo.password})
insert>
测试:
@Test
void add() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangsan");
userInfo.setPassword("123");
int effect = userMapper.add(userInfo);
System.out.println("影响的行数:" + effect);
}
增加数据的时候返回自增主键
在 insert 标签属性中添加 useGeneratedKeys=“true” keyProperty=“id”
<insert id="add" useGeneratedKeys="true" keyProperty="id">
useGeneratedKeys="true"
:这个属性用于指定是否使用数据库自动生成的主键。当设置为 true
时,表示插入数据后,将从数据库获取生成的主键值,并将其设置到相应的属性中。keyProperty="id"
:如果 useGeneratedKeys
设置为 true
,则需要使用 keyProperty
属性指定将生成的主键值设置到哪个 Java 对象的属性中。在这个例子中,表示将生成的主键值设置到 Java 对象的 id
属性中。测试:
@Test
void add() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangsan");
userInfo.setPassword("123");
int effect = userMapper.add(userInfo);
System.out.println("影响的行数:" + effect + " id: " + userInfo.getId());
}
// 输出:影响的行数:1 id: 3
在 xml 中分别使用的是 update 标签和 delete 标签,除此以外,其他的关于 MyBatis 的操作都和查询、增加一样,这里不再赘述。
#{} 和 ${} 在构造 SQL 语句的方式上存在差异:
#{}
表达式(预编译):
#{}
表达式是 MyBatis 中用于预编译参数的占位符。这种方式会在 SQL 语句中使用占位符 ?
,然后通过预编译的方式向 SQL 语句中传递参数。这样可以有效防止 SQL 注入攻击。${}
表达式(字符串拼接):
${}
表达式是用于字符串拼接的方式。在 SQL 语句中,${}
会直接替换成参数的字符串表示形式,不会进行预编译。预编译的方式具有以下优点:
绝大部分场景都是使用 #{}
,但是也有部分场景适合使用 ${}
比如我要传递的参数是 SQL 中的关键字,比如 asc 和 desc
<select id="getAllByOrder" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo order by ${order}
select>
如果使用 #{}
则会将关键字识别成字符串,在最终生成的 SQL 语句中画蛇添足地增加 ''
,比如 select * from userinfo order by 'desc'
导致 SQL 语法错误。直接使用 ${}
字符串拼接的方式就简便很多。
而且我们可以在代码中 if 判断,保证传入的参数只能是 asc 和 desc,从而防止 SQL 注入。
模糊查询获取参数也必须用 #{}
,不能用 ${}
因为传入的参数是无法穷举的,有 SQL 注入的风险。
而 #{}
会画蛇添足,和通配符 %
_
配合使用不太方便。
<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo
where username like '%#{pattern}%'
select>
以上代码最后构造的 SQL 语句形如 select * from userinfo where username like '%'s'%'
解决方法:
要么写成下面的形式,%
直接通过 Java 代码传入:
<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo
where username like #{pattern}
select>
Java 代码:
List<UserInfo> list = userMapper.searchUsers("%s%");
这样自动添加 ''
后就正确了。
要么使用 concat:
<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
select * from userinfo
where username like concat('%', #{pattern}, '%')
select>
Java 代码:
List<UserInfo> list = userMapper.searchUsers("s");
resultMap
是一种用于映射查询结果集的配置元素。它允许你定义一个映射规则,将数据库查询结果的列映射到 Java 对象的属性中。
简单来说,这种方式可以让 Java 代码中的类的属性名,和数据库中的字段名在不一致的情况下也能一一对应。
<resultMap id="userResultMap" type="org.example.mybatisdemo.model.UserInfo">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="password" column="user_password"/>
resultMap>
id
元素用于定义主键映射,property
指定了 Java 对象中的属性名,column
指定了数据库结果集中的列名。result
元素用于定义其他属性的映射规则。<select id="getUserById" resultMap="userResultMap">
select * from users where user_id = #{userId}
select>
getUserById
查询将使用名为 userResultMap
的 resultMap
进行结果映射。MyBatis 将查询结果中的列按照 resultMap
中定义的映射规则进行映射,从而创建一个包含查询结果的 Java 对象。
动态 SQL 是指在 SQL 语句中根据不同的条件或参数动态生成不同的 SQL 片段的技术。在 MyBatis 中,动态 SQL 主要通过使用
等 XML 元素来实现。
标签用于在 SQL 语句中根据条件动态包含或排除某段 SQL 语句。
基本的
标签语法如下:
<if test="condition">
if>
test
属性用于指定一个条件表达式,如果条件表达式为真,那么
内部定义的 SQL 语句片段将会被包含在最终的 SQL 语句中。如果条件表达式为假,则这部分 SQL 语句将被忽略。
例子:
<select id="getUserByCondition" resultType="User">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND username = #{username}
if>
<if test="password != null and password != ''">
AND password = #{password}
if>
where>
select>
标签,用于在生成 SQL 语句时处理多余的空白字符,同时可以根据条件来动态添加或移除 SQL 片段的开头或结尾。
元素的基本语法结构如下:
<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
trim>
prefix
属性用于指定在 SQL 语句片段前添加的内容。prefixOverrides
属性用于指定需要从 SQL 语句片段开头移除的前缀,可以是逗号分隔的前缀列表。suffix
属性用于指定在 SQL 语句片段后添加的内容。suffixOverrides
属性用于指定需要从 SQL 语句片段结尾移除的后缀,可以是逗号分隔的后缀列表。示例:
<select id="getUserByCondition" resultType="User">
select * from users
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null and username != ''">
and username = #{username}
if>
<if test="password != null and password != ''">
or password = #{password}
if>
trim>
select>
在这个例子中,
元素的作用是处理 WHERE
子句,根据动态条件来生成合适的查询语句。具体解释如下:
prefix="where"
指定了在 SQL 语句片段前添加的内容,即添加了 where
。prefixOverrides="and | or"
指定了需要从 SQL 语句片段开头移除的前缀,即移除了可能出现的多余的 and
或 or
。
元素内部,根据动态条件使用
元素生成相应的查询条件。假设在执行这个查询时,传入的参数是 username="John"
,password=null
,最终生成的 SQL 语句会是:
select * from users
where
username = 'John'
标签用于在 where 子句中动态生成条件。
元素会自动处理生成的条件之间的逻辑关系,避免不必要的 and 或 or 连接符。
基本的
标签语法如下:
<select id="getUserByCondition" resultType="User">
SELECT * FROM users
<where>
where>
select>
其示例在 if 标签已经涉及到。
例:
<select id="selectUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name = #{name}
if>
<if test="age != null">
AND age = #{age}
if>
where>
select>
假设传入的参数是 name = 'John'
和 age = 25
,生成的 SQL 语句将类似于:
SELECT * FROM users
WHERE
name = 'John'
AND age = 25;
如果只传入了 name = 'John'
,而没有传入 age
参数,生成的 SQL 语句将是:
SELECT * FROM users
WHERE
name = 'John';
如果两个条件都没有被传入,生成的 SQL 语句将仅包含基本的 SELECT 语句,而不包含 WHERE 子句:
SELECT * FROM users;
where 标签更简单地完成了上面 trim 标签的例子
元素针对 update 语句。它主要用于处理动态更新表中的列,根据传入的参数动态生成 set 子句。
元素可以使得在更新记录时只更新传入的非空字段,从而避免更新所有字段,提高了 SQL 语句的灵活性。
例子:
<update id="updateUser">
UPDATE users
<set>
<if test="name != null">
name = #{name},
if>
<if test="age != null">
age = #{age},
if>
set>
WHERE id = #{id}
update>
例如,如果调用上述的更新方法时传入了 name = 'John'
和 age = 25
,生成的 SQL 语句将类似于:
UPDATE users
SET
name = 'John',
age = 25
WHERE id = #{id};
如果只传入了 name = 'John'
而没有传入 age
参数,生成的 SQL 语句将是:
UPDATE users
SET
name = 'John'
WHERE id = #{id};
通常用于动态生成 SQL 语句中的 IN 子句,允许我们在查询或更新操作中使用一组值。
主要用于遍历集合或数组,并根据集合中的元素动态生成相应的 SQL 片段。
例子:
<select id="selectUsersByIds" resultType="User" parameterType="java.util.List">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
foreach>
select>
在上述例子中,
元素用于循环遍历传入的 ids
集合,并根据集合中的元素动态生成 IN 子句。其中,collection
属性指定了要迭代的集合,item
属性指定了集合中的元素变量名,open
属性定义了 IN 子句的开头,separator
属性定义了元素之间的分隔符,close
属性定义了 IN 子句的结尾。
例如,如果传入的 ids
集合为 [1, 2, 3]
,生成的 SQL 语句将类似于:
SELECT * FROM users
WHERE id IN (1, 2, 3);
元素也可以用于其他操作,比如在插入操作中批量插入数据。下面是一个简单的插入示例:
<insert id="insertUsers" parameterType="java.util.List">
INSERT INTO users (name, age)
VALUES
<foreach collection="list" item="user" separator="," >
(#{user.name}, #{user.age})
foreach>
insert>
元素用于循环遍历传入的 list
集合,并根据集合中的元素动态生成插入语句。这种方式可以在一次数据库操作中插入多条记录,提高了效率。