MyBatis全方位指南:从注解到XML文件的数据库操作


目录

一.什么是MyBatis

入门程序初体验

二.MyBatis基本操作CRUD

▐ 增(Insert)

返回主键

▐ 删(Delete)

▐  改(Update)

▐ 查(Select)

起别名

结果映射

开启驼峰命名(推荐)

三.MyBatis XML配置文件

▐ 增(Insert)

▐ 删(Delete)

▐ 改(Update)

▐ 查(Select)

#{} 和 ${}


一.什么是MyBatis

MyBatis是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。持久层:指的就是持久化操作的层, 通常指数据访问层(dao),是⽤来操作数据库的。

在使用前需要导入相关依赖,首先是MyBatis依赖

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

 还有MySQL的依赖

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

 yml中相关配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/数据库表名?useSSL=false&serverTimezone=UTC
    username: 数据库用户名
    password: 数据库密码
    driver-class-name: com.mysql.cj.jdbc.Driver

如果使⽤ MySQL 是 5.x 之前的版本,驱动类使⽤的是"com.mysql.jdbc.Driver",如果是⼤于 5.x 使⽤的是“com.mysql.cj.jdbc.Driver”

入门程序初体验

准备好数据库和对应的实体类:

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo`
(
    `id`          INT(11)      NOT NULL AUTO_INCREMENT,
    `username`    VARCHAR(127) NOT NULL,
    `password`    VARCHAR(127) NOT NULL,
    `age`         TINYINT(4)   NOT NULL,
    `gender`      TINYINT(4)  DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',
    `phone`       VARCHAR(15) DEFAULT NULL,
    `delete_flag` TINYINT(4)  DEFAULT 0 COMMENT '0-正常, 1-删除',
    `create_time` DATETIME    DEFAULT now(),
    `update_time` DATETIME    DEFAULT now(),
    PRIMARY KEY (`id`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('admin', 'admin', 18, 1, '18612340001');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('zhangsan', 'zhangsan', 18, 1, '18612340002');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('lisi', 'lisi', 18, 1, '18612340003');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('wangwu', 'wangwu', 18, 1, '18612340004');

实体类

import lombok.Data;
import java.util.Date;

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

 下面通过一段程序可以快速体验什么是MyBatis,相比传统使用JDBC的方式链接操作数据库,使用MyBatis只需要用2行代码就解决了问题

@Mapper
public interface UserInfoMapper {
    //查询所有的用户
    @Select("select username, `password`, age, gender, phone from userinfo")
    public List<UserInfo> queryAllUser();
}

@Mapper注解:表⽰是MyBatis中的Mapper接⼝

@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容

queryAllUser() 方法被调用的时候,就会自动执行@Select注解中的SQL语句

@Slf4j
@SpringBootTest
class MyBatisDemoApplicationTests {
    
    @Autowired
    private UserInfoMapper userInfoMapper;
    
    @Test
    void contextLoads() {
        List<UserInfo> userInfos = userInfoMapper.queryAllUser();
        log.info("用户信息如下:" + userInfos);
    }
}

试运行则可以看见查询结果

 但很显然,这样是不方便观察的,为此我们可以继续在yml中添加配置项,让日志记录更加方便我们阅读(在yml文件中配置项一定要保持缩进的一致,不然可能会报错)

mybatis:
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

再次运行,就可以更直观的看到SQL运行结果了

二.MyBatis基本操作CRUD

对于数据库的操作基本上概括来说就是增删改查四大部分,也就是我们常说的CRUD,不同的点只是传递的参数不同。

在使用MyBatis的时候,我们也可以向方法中传参,通过 #{ } 就可以拿到方法参数列表中的参数。

建议和参数名保持⼀致

    /**
     * 根据ID查找用户
     * @param id
     * @return
     */
    @Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
    UserInfo queryById(Integer id);

如果参数名不一致,我们也可以通过注解的方式来实现需求,我们可以通过 @Param 设置参数的别名,如果使用 @Param 设置别名,#{...} 里面的属性名必须和 @Param 设置的⼀样

    /**
     * 根据ID查找用户
     * @param id
     * @return
     */
    @Select("select username, `password`, age, gender, phone from userinfo where id= #{userid} ")
    UserInfo queryById(@Param("userid") Integer id);

▐ 增(Insert)

SQL语句:

insert into userinfo (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")

Mapper接口:

我们可以将对象作为参数,在调用的时候因为对象的字段和数据库的字段是一一对应的,所以可以直接通过对象里面的属性给SQL里面的字段赋值

    /**
     * 插入用户
     * @param userInfo
     * @return
     */
    @Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
    Integer insert(UserInfo userInfo);

调用测试:

    @Test
    void insert() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("zhaoliu");
        userInfo.setPassword("zhaoliu");
        userInfo.setGender(2);
        userInfo.setAge(21);
        userInfo.setPhone("18612340005");
        userInfoMapper.insert(userInfo);
    }

返回主键

insert语句默认返回的是受影响的行数,如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解

    /**
     * 插入用户信息,返回主键值
     * @param userInfo
     * @return
     */
    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
    Integer insert(@Param("userinfo") UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false.
  • keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)

测试

    @Test
    void insert() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("zhaoliu");
        userInfo.setPassword("zhaoliu");
        userInfo.setGender(2);
        userInfo.setAge(21);
        userInfo.setPhone("18612340005");
        Integer count = userInfoMapper.insert(userInfo);
        System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());
    }

注意: 设置 useGeneratedKeys=true 之后, ⽅法返回值依然是受影响的⾏数,⾃增id 会设置在上述 keyProperty 指定的属性中

▐ 删(Delete)

SQL语句:

delete from userinfo where id=6

Mapper接⼝:

    /**
     * 根据ID删除用户
     * @param id
     */
    @Delete("delete from userinfo where id = #{id}")
    void delete(Integer id);

▐  改(Update)

SQL语句:

 update userinfo set username="zhaoliu" where id=5

Mapper接口:

    /**
     * 更新操作
     * @param userInfo
     */
    @Update("update userinfo set username=#{username} where id=#{id}")
    void update(UserInfo userInfo);

▐ 查(Select)

查询语句在开发中占比非常大,在入门程序中我们已经体会了查询的基本用法,对于有几点需要额外注意

在数据库中的数据和实体类交互的时候,只有双方字段完全一模一样的时候,数据才会正常导入其中,在前面的数据库表和实体类中,其实有几个字段并没有做到完全的映射,这就导致了传入数据的时候往往结果是null

为了解决这样的问题,我们提供了以下三种解决方案

  • 起别名
  • 结果映射
  • 开启驼峰命名

起别名

我们可以通过SQL语句中的 AS 来讲将数据库表字段和实体类字段进行一一对应,如下所示

    /**
     * 查询全部用户(起别名)
     * @return
     */
    @Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " +
            "create_time as createTime, update_time as updateTime from userinfo")
    public List<UserInfo> queryAllUser();

PS:SQL语句太长可以用 " + "拼接

结果映射

通过@Results注解,我们可以将查询后的结果映射到实体类字段中

    /**
     * 查询全部用户(结果映射)
     * @return
     */
    @Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
    @Results({
            @Result(column = "delete_flag",property = "deleteFlag"),
            @Result(column = "create_time",property = "createTime"),
            @Result(column = "update_time",property = "updateTime")
    })
    List<UserInfo> queryAllUser();

如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称

使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap

    @Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
    @Results(id = "resultMap",value = {
            @Result(column = "delete_flag",property = "deleteFlag"),
            @Result(column = "create_time",property = "createTime"),
            @Result(column = "update_time",property = "updateTime")
    })
    List<UserInfo> queryAllUser();
    
    @Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " +
            "from userinfo where id= #{userid} ")
    @ResultMap(value = "resultMap")
    UserInfo queryById(@Param("userid") Integer id);

开启驼峰命名(推荐)

其实上述三种方法多多少少都是有点麻烦的,为了方便省事,我们可以通过配置项来解决问题

通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词),⽽ Java 属性⼀般遵循驼峰命名法约定,为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

mybatis:
  configuration:
    map-underscore-to-camel-case: true #配置驼峰⾃动转换
  • 驼峰命名规则: abc_xyz => abcXyz
  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz

而Java代码则不需要进行任何处理,使用这种方式需要注意实体类名称命名必须规范

三.MyBatis XML配置文件

对于一些较为复杂的SQL语句,写在注解里面会有很大程度的不方便,MyBatis为我们提供了通过XML文件映射的方式来实现SQL语句,将SQL语句和逻辑写在一个统一的XML文件中,当Java代码需要执行MyBatis的方法的时候,就会自动从这个XML文件中进行映射,然后拿到想要的SQL语句并且执行。

主要通过以下俩步进行完成

  • 配置数据库连接字符串和MyBatis
  • 写持久层代码

首先,需要在配置文件中配置XML文件的位置

mybatis:
  mapper-locations: classpath:mapper/*.xml

classpath 代表 resources 这个文件夹,即XML文件位置在 resources 下的 mapper 文件夹下的所有 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="com.luming.mybatisdemo.mapper.UserInfoXMLMapper">


</mapper>

前面俩行是固定格式,代表我们要使用 mybatis 中的这个功能,在<mapper>标签里面就是我们需要进行编码的地方,namespace 属性代表着这个XML文件对应的 Interface 接口的相对路径,可以直接复制其引用。

然后在其中,我们就可以通过不同的标签来实现不同的SQL,在具体的标签里面则只需要写具体的SQL语句,如下就是一个简单的查询全部用户信息的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="com.luming.mybatisdemo.mapper.UserInfoXMLMapper">

    <select id="queryAllUser" resultType="com.luming.mybatisdemo.pojo.UserInfo">
        select username,`password`, age, gender, phone from userinfo
    </select>
    
</mapper>

<select> 查询标签:是⽤来执⾏数据库的查询操作的:

  • id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
  • resultType :是返回的数据类型,也就是我们定义的实体类(还是复制引用即可)

我们可以看到,XML文件和接口之间的对应关系(图中俩个小鸟),蓝色小鸟是接口,红色小鸟是XML文件。这是MyBatisX插件所提供的功能,供我们方便的跳转编码,还可以提供自动生成部分代码的功能。

通过XML来实现CRUD的各部分案例在下面给出

▐ 增(Insert)

UserInfoMapper接⼝

    Integer insertUser(UserInfo userInfo);

UserInfoMapper.xml实现

    <insert id="insertUser">
        insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})
    </insert>

如果使⽤@Param设置参数名称的话, 使⽤⽅法和注解类似

    Integer insertUser(@Param("userinfo") UserInfo userInfo);
    <insert id="insertUser">
        insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})
    </insert>

如果需要返回⾃增 id,接⼝定义不变 Mapper.xml 设置 useGeneratedKeys 和keyProperty属性即可

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})
    </insert>

▐ 删(Delete)

UserInfoMapper接⼝

    Integer deleteUser(Integer id);

UserInfoMapper.xml实现

    <delete id="deleteUser">
        delete from userinfo where id = #{id}
    </delete>

▐ 改(Update)

UserInfoMapper接⼝

    Integer updateUser(UserInfo userInfo);

UserInfoMapper.xml实现

    <update id="updateUser">
        update userinfo set username=#{username} where id=#{id}
    </update>

▐ 查(Select)

同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题,解决办法和注解类似:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

其中起别名和驼峰命名和之前讲的完全一致,所以这里只会结果映射这一种方案讲解

    <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
        <id column="id" property="id"></id>
        <result column="delete_flag" property="deleteFlag"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>
    </resultMap>
    
    <select id="queryAllUser" resultMap="BaseMap">
        select id, username,`password`, age, gender, phone, delete_flag,
               create_time, update_time from userinfo
    </select>

#{} 和 ${}

MyBatis 参数赋值有两种⽅式,即 #{ } 和 ${ } 。在JDBC中存在俩种SQL的执行操作,分别是直接执行SQL和预编译SQL。#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别。

#{ } 使⽤的是预编译SQL,通过 ? 占位的⽅式提前对SQL进⾏编译,然后把参数填充到SQL语句中, #{ } 会根据参数类型自动拼接引号 ' '

${ } 会直接进⾏字符替换,⼀起对SQL进⾏编译,如果参数为字符串,需要加上引号 ' '。

当客⼾发送⼀条SQL语句给服务器后,⼤致流程如下:

  • 解析语法和语义, 校验SQL语句是否正确
  • 优化SQL语句, 制定执⾏计划
  • 执⾏并返回结果

⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

绝⼤多数情况下,某⼀条 SQL 语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同)。 如果每次都需要经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了。

与上述相对应的就是预编译SQL,即编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译(只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率。

同时预编译一定程度上还可以有效的防止了SQL注入的安全风险。

总的来说,#{ }可以有效的提高SQL执行效率,并且可以避免SQL注入的风险。

但这并不是说我们凡事就应该都用#{ },对于一些特殊的SQL语句可能会有对引号的要求,整个时候我们就需要谨慎使用了,如下进行排序查询的时候,由于SQL语法规定排序规则不需要加引号,所以这时就需要使用${ }。

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
        "from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
        "from userinfo order by id #{sort} ")
List<UserInfo> queryAllUserBySort(String sort);



 本次的分享就到此为止了,希望我的分享能给您带来帮助,创作不易也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luming.02

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值