MyBatis入门操作

文章目录

  • [1. MyBatis入门](#1. MyBatis入门)
    • [1.1 打印日志](#1.1 打印日志)
    • [1.2 参数传递](#1.2 参数传递)
  • [2. 注释开发模式](#2. 注释开发模式)
    • [2.1 增(Insert)](#2.1 增(Insert))
    • [2.2 删(Delete)](#2.2 删(Delete))
    • [2.3 改(Update)](#2.3 改(Update))
    • [2.4 查(Select)](#2.4 查(Select))
      • [2.4.1 起别名](#2.4.1 起别名)
      • [2.4.2 结果映射](#2.4.2 结果映射)
      • [2.4.3 开启驼峰命名(推荐)](#2.4.3 开启驼峰命名(推荐))
  • [3. XML开发模式](#3. XML开发模式)
    • [3.1 增(Insert)](#3.1 增(Insert))
    • [3.2 删(Delete)](#3.2 删(Delete))
    • [3.3 改(Update)](#3.3 改(Update))
    • [3.4 查(Select)](#3.4 查(Select))
  • [4. #{} 和 {}](#{} 和 {})

1. MyBatis入门

MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发

持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是用来操作数据库的

Mybatis操作数据库的步骤:

  1. 准备工作(创建springboot工程、数据库表准备、实体类)
  2. 引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)
  3. 编写SQL语句(注解/XML)
  4. 测试

准备工作: 创建springboot工程,并导入 mybatis的起步依赖、mysql的驱动包; 创建用户表, 并创建对应的实体类User, 实体类的属性名与表中的字段名一一对应

配置数据库连接字符串

yaml 复制代码
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

1.1 打印日志

在Mybatis当中我们可以借助日志, 查看到sql语句的执行、执行传递的参数以及执行结果, 在配置文件中进行配置即可

yaml 复制代码
mybatis:
  configuration: # 配置打印 MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.2 参数传递

需求: 查找id=4的用户,对应的SQL就是: select * from user_info where id=4, 只能查找id=4 的数据, 所以SQL语句中的id值不能写成固定数值,需要变为动态的数值;

解决方案:在queryById方法中添加一个参数(id),将方法中的参数,传给SQL语句使用 #{} 的方式获取方法中的参数

java 复制代码
    @Select("select username, `password`, age, gender, phone from user_info where id= #{id}")
    UserInfo queryById(Integer id);

这里password加反引号是防御性编程, 不加也能运行

如果mapper 接口方法形参 只有一个普通类型的参数,#{...} 里面的属性名可以随便写,如:#{id}、#{value}。建议和参数名保持一致 ;

也可以通过 @Param , 设置参数的别名 , 如果使用 @Param 设置别名, #{...}里面的属性名必须和@Param 设置的一样

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id= #{userid} ")
    UserInfo queryById(@Param("userid") Integer id);

2. 注释开发模式

2.1 增(Insert)

sql: insert into user_info (username, password, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")

Mapper接口:

java 复制代码
@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);

如果设置了 @Param 属性 , #{...} 需要使用 参数.属性 来获取

java 复制代码
    @Insert("insert into user_info (username, `password`, age, gender, phone) values " +
            "(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
    Integer insert(@Param("userInfo") UserInfo userInfo);

2.2 删(Delete)

SQL : delete from user_info where id = 6

Mapper接口:

java 复制代码
    @Delete("delete from user_info where id = #{id}")
    void delete(Integer id);

2.3 改(Update)

SQL : update user_info set username='zhaoliu' where id = 5

Mapper接口:

java 复制代码
@Update("update user_info set username=#{username} where id=#{id}")
    void update(UserInfo userInfo);

2.4 查(Select)

java 复制代码
    @Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time form user_info")
    List<UserInfo> queryAllUser();

MyBatis 会根据方法的返回结果进行赋值:

方法用对象 UserInfo接收返回结果, MySQL 查询出来数据为一条, 就会自动赋值给对象,

方法用List接收返回结果, MySQL 查询出来数据为一条或多条时, 也会自动赋值给List;

但如果MySQL 查询返回多条, 但是方法使用UserInfo接收, MyBatis执行就会报错.

当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性

但是如果数据库表字段为delete_flag, create_time这种, 使用Java类属性却为delateFlag, createTime, 此时就对应不上, 我们的查询结果中这几个字段就会为null

解决办法有三个

2.4.1 起别名

java 复制代码
@Select("select id, username, `password`, age, gender, phone, " +
            "delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info")
    public List<UserInfo> queryAllUser();

2.4.2 结果映射

java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time form user_info")
    @Results({
            @Result(column = "delete_flag",property = "deleteFlag"),
            @Result(column = "create_time",property = "createTime"),
            @Result(column = "update_time",property = "updateTime")
    })
    List<UserInfo> queryAllUser();

如果其他SQL, 也希望可以复用这个映射关系, 可以给这个Results定义一个名称, 这样其他SQL用@Result时能做到代码复用

2.4.3 开启驼峰命名(推荐)

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

yaml 复制代码
mybatis:
  configuration:
    map-underscore-to-camel-case: true      # 驼峰命名自动映射

3. XML开发模式

XML模式持久层代码要先进行方法Interface定义, 再在XXX.xml文件中写具体实现

添加mapper接口

java 复制代码
@Mapper
public interface UserInfoMapper {
    List<UserInfo> queryAllUser();
}

.xml文件实现

数据持久成的实现,MyBatis 的固定 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.example.test.demos.Test.UserInfoMapper">


</mapper>

在xml文件中书写具体sql

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.example.test.demos.Test.UserInfoMapper">
    
    <select id="queryAllUser" resultType="com.example.test.demos.dao.UserInfo">
        select username,`password`, age, gender, phone from user_info
    </select>
    
</mapper>

<mapper> 标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接口的全限定名,包括全包名.类名

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

◦ id :是和 Interface (接口)中定义的方法名称一样的,表示对接口的具体实现方法。

◦ resultType :是返回的数据类型,也就是开头我们定义的实体类.

3.1 增(Insert)

mapper接口

java 复制代码
Integer insertUser(UserInfo userInfo);

xml实现

xml 复制代码
<insert id="insertUser">
        INSERT into userinfo (username, `password`, age, gender, phone)
        Values (#{username}, #{password}, #{age},#{gender},#{phone})
    </insert>

如果使用@Param设置参数名称的话, 同样需要用参数.属性 来获取

3.2 删(Delete)

mapper接口

java 复制代码
    Integer deleteUser(Integer id);

xml实现

xml 复制代码
<delete id="deleteUser">
    delete from user_info where id = #{id}
</delete>

3.3 改(Update)

mapper接口

java 复制代码
Integer updateUser(UserInfo userInfo);

xml实现

xml 复制代码
<update id="updateUser">
        update user_info set username=#{username} where id=#{id}
</update>

3.4 查(Select)

前面举例已经书写了一个xml的查询接口实现

同样的, 使用XML 的方式进行查询, 也存在数据封装的问题, 解决办法参考注解的解决方法, 其中起别名和开启驼峰命名和注解一样, 此处介绍下xml如何写结果映射

xml 复制代码
<resultMap id="BaseMap" type="com.example.test.demos.dao.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 user_info
    </select>

4. #{} 和 ${}

#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别

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

${} 使用的是即时SQL, 会直接进行字符替换, 一起对SQL进行编译. 如果参数为字符串, 需要加上引号 ' '

当客户发送一条SQL语句给服务器后, 大致流程如下:

  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执行计划
  3. 执行并返回结果
    一条 SQL如果走上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

JDBC, #{} 使用的是预编译SQL, 使用 ? 占位对SQL进行编译

预编译SQL对比即时SQL

  1. 性能更高

    绝大多数情况下, 某一条 SQL 语句可能会被反复调用执行, 或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同, update 的 set 子句值不同, insert 的 values 值不同). 如果每次都需要经过上面的语法解析, SQL优化, SQL编译等,则效率就明显不行了.

    预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同), 省去了解析优化等过程, 以此来提高效率

  2. 更安全(防止SQL注入)

    SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法. 例如: ' or 1 = '1''

    所以用于查询的字段,尽量使用 #{} 预查询的方式

从上面的例子中, 可以得出结论: {} 会有SQL注入的风险, 所以我们尽量使用#{}完成查询, 那 {} 还有什么可使用的场景呢?

排序功能

使用 ${sort} 可以实现排序查询, 而使用 #{sort} 就不能实现排序查询了

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
            "from user_info order by id ${sort} ")
    List<UserInfo> queryAllUserBySort(String sort);

注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的${sort} 也不加引号

但是当我们使用 #{sort} 查询时, asc 前后会自动给加了引号, 导致 sql 错误#{} 会根据参数类型判断是否拼接引号 ' '

如果参数类型为String, 就会加上 引号

除此之外, 还有表名作为参数时, 也只能使用 ${}

like查询

like 使用 #{} 报错

虽然把 #{} 改成 {} 可以正确查出来, 但是 {}存在SQL注入的问题, 所以不能直接使用 ${} ;

使用 mysql 的内置函数 concat() 来处理, #{} + CONCAT

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
            "from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

当然这里个人觉得使用MP的like查询更方便

总结:

#{} 和${} 区别

  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防止SQL注入, ${}存在SQL注入的风险, 查询语句中, 推荐使用 #{}
  3. 但是一些场景, #{} 不能完成, 比如 排序功能, 表名, 字段名作为参数时, 这些情况需要使用${}
  4. 模糊查询虽然${}可以完成, 但是存在SQL注入问题, 所以通常使用mysql内置函数concat来完成
相关推荐
xcLeigh1 小时前
Go入门:短变量声明的陷阱与最佳实践
java·redis·golang·教程·变量
青山木1 小时前
Hot 100 --- 最小栈
java·数据结构·算法·leetcode
疯狂打码的少年1 小时前
【数据结构】栈的应用:表达式求值(后缀表达式)
java·数据结构·笔记·算法
szephyr1 小时前
腾讯云 ADP 智能体的 Skills 版本回滚总是回到旧配置,是缓存没清还是版本管理没开?
java·缓存·腾讯云
云烟成雨TD1 小时前
Micrometer 系列【39】链路追踪:入门案例 | 环境准备
java·云原生·链路追踪
chuan.bai1 小时前
Java RAG 实战(第 3 篇):从交互式聊天到多轮上下文
java·人工智能·macos·ai
952361 小时前
Sentinel
java·后端·spring·sentinel·springcloud
阿pin1 小时前
Java随笔-JDK7 HashMap头插法为何能导致死循环?
java·开发语言·hashmap
李可以量化2 小时前
Tornado 从了解到精通(一)下:异步与非阻塞 IO 核心原理
java·数据库·tornado