MyBatis——Java 持久层框架
文章目录
MyBatis 是什么
- 是一款持久层框架
- 基于 JDBC
- 可以通过方便的设置实现数据库操作
Mybatis(之前称为 iBatis)也是一个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。
ORM 把数据库映射为对象:
- 数据库表(table)–>类(class)
- 记录(record,行数据)–>对象(object)
- 字段(field)–>对象的属性(attribute)
准备工作
创建一个数据库和表
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
引入之后,我们的项目是启动不了的,还需要进行配置。
配置连接字符串和 MyBatis
配置 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 组成
MyBatis 主要由两部分组成:
- 接口(Mapper 接口): MyBatis 的接口用于定义数据库操作的方法。每个接口方法通常对应一个数据库操作,比如查询、插入、更新或删除数据。这些接口方法的签名定义了输入参数和返回类型,而实际的 SQL 查询则是通过 XML 文件或注解来配置的。
- XML 文件(Mapper XML 文件): MyBatis 的 XML 文件用于存放 SQL 映射配置,即将接口方法与实际的 SQL 语句进行关联。XML 文件中包含了数据库操作的具体 SQL 语句,以及输入参数和输出结果的映射关系。每个 Mapper 接口通常对应一个同名的 XML 文件。
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;
}
创建 MyBatis 接口(以查询所有为例)
@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 文件(实现接口中的所有方法)
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="">
</mapper>
namespace
里填要实现的接口的路径。
例:mapper 里面添加 select 标签,表示要实现查询方法,id 指定实现的方法名(和接口中的一致),result 指定返回的记录的类型,标签内部填写具体的 sql 语句
<?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="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 语句的结构。这有助于防止恶意构造的参数值破坏 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 注入。
like 查询
模糊查询获取参数也必须用 #{}
,不能用 ${}
因为传入的参数是无法穷举的,有 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
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 语句中根据不同的条件或参数动态生成不同的 SQL 片段的技术。在 MyBatis 中,动态 SQL 主要通过使用 <if>、<choose>、<when>、<otherwise>、<trim>、<where>、<set>
等 XML 元素来实现。
<if>
<if>
标签用于在 SQL 语句中根据条件动态包含或排除某段 SQL 语句。
基本的 <if>
标签语法如下:
<if test="condition">
<!-- 要包含的 SQL 语句片段 -->
</if>
test
属性用于指定一个条件表达式,如果条件表达式为真,那么 <if>
内部定义的 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>
<trim>
<trim>
标签,用于在生成 SQL 语句时处理多余的空白字符,同时可以根据条件来动态添加或移除 SQL 片段的开头或结尾。
<trim>
元素的基本语法结构如下:
<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
<!-- SQL 语句片段 -->
</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>
在这个例子中,<trim>
元素的作用是处理 WHERE
子句,根据动态条件来生成合适的查询语句。具体解释如下:
prefix="where"
指定了在 SQL 语句片段前添加的内容,即添加了where
。prefixOverrides="and | or"
指定了需要从 SQL 语句片段开头移除的前缀,即移除了可能出现的多余的and
或or
。- 在
<trim>
元素内部,根据动态条件使用<if>
元素生成相应的查询条件。
假设在执行这个查询时,传入的参数是 username="John"
,password=null
,最终生成的 SQL 语句会是:
select * from users
where
username = 'John'
<where>
<where>
标签用于在 where 子句中动态生成条件。<where>
元素会自动处理生成的条件之间的逻辑关系,避免不必要的 and 或 or 连接符。
基本的 <where>
标签语法如下:
<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 标签的例子
<set>
<set>
元素针对 update 语句。它主要用于处理动态更新表中的列,根据传入的参数动态生成 set 子句。<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};
<foreach>
<foreach>
通常用于动态生成 SQL 语句中的 IN 子句,允许我们在查询或更新操作中使用一组值。<foreach>
主要用于遍历集合或数组,并根据集合中的元素动态生成相应的 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>
在上述例子中,<foreach>
元素用于循环遍历传入的 ids
集合,并根据集合中的元素动态生成 IN 子句。其中,collection
属性指定了要迭代的集合,item
属性指定了集合中的元素变量名,open
属性定义了 IN 子句的开头,separator
属性定义了元素之间的分隔符,close
属性定义了 IN 子句的结尾。
例如,如果传入的 ids
集合为 [1, 2, 3]
,生成的 SQL 语句将类似于:
SELECT * FROM users
WHERE id IN (1, 2, 3);
<foreach>
元素也可以用于其他操作,比如在插入操作中批量插入数据。下面是一个简单的插入示例:
<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>
<foreach>
元素用于循环遍历传入的 list
集合,并根据集合中的元素动态生成插入语句。这种方式可以在一次数据库操作中插入多条记录,提高了效率。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!