【Mybatis】Mybatis入门 && 基础操作 && XML配置文件开发 && 多表查询 && 注入问题 && 数据库连接池

文章目录

  • [Ⅰ. 什么是 MyBatis](#Ⅰ. 什么是 MyBatis)
  • [Ⅱ. MyBatis入门 -- `@Mapper`](#Ⅱ. MyBatis入门 -- @Mapper)
  • [Ⅲ. MyBatis的基础操作](#Ⅲ. MyBatis的基础操作)
  • [Ⅳ. MyBatis XML配置文件开发](#Ⅳ. MyBatis XML配置文件开发)
  • [Ⅴ. 多表查询](#Ⅴ. 多表查询)
  • [Ⅵ. `#{}` 和 `{}\`](#{}` 和 `{}`)
  • [Ⅶ. 数据库连接池](#Ⅶ. 数据库连接池)

Ⅰ. 什么是 MyBatis

官网:MyBatis中文网

  • MyBatis 是一款优秀的持久层框架,用于简化JDBC的开发。
  • MyBatis 本是 Apache 的一个开源项目 iBatis,2010 年这个项目由 apache 迁移到了 googlecode,并且改名为 MyBatis。2013 年 11 月迁移到 Github。

在上面我们提到一个词:持久层

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

简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具。

Ⅱ. MyBatis入门 -- @Mapper

Mybatis 操作数据库的步骤:

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

在项目中,创建持久层接口 UserInfoMapper:

java 复制代码
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

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

Mybatis的持久层接口规范一般都叫 XxxMapper

@Mapper 注解:表示是 MyBatis 中的 Mapper 接口

  • 程序运行时,框架会自动生成接口的实现类对象(代理对象),并给交 Spring 的 IOC 容器管理
  • @Select 注解:代表的就是 select 查询,也就是注解对应方法的具体实现内容。

Ⅲ. MyBatis的基础操作

一、打印日志

Mybatis 当中我们可以借助日志,查看到 sql 语句的执行、执行传递的参数以及执行结果。

在配置文件中进行配置即可:

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

如果是 application.properties,配置内容如下:

properties 复制代码
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

重新运行程序,可以看到SQL执行内容,以及传递参数和执行结果:

①:查询语句

②:传递参数及类型

③:SQL执行结果

二、参数传递:#{} && @Param

需求:查找 id=4 的用户,对应的 sql 就是:select * from user_info where id=4

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

但是这样的话,只能查找 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);

要绑定的参数建议和方法形参名保持一致

但是如果 mapper 接口方法形参只有一个普通类型的参数,#{...}里面的属性名可以随便写。

当然也可以通过 @Param 设置参数的别名 。如果使用了 @Param 设置别名,则 #{...} 里面的属性名必须和 @Param 设置的一样,如下所示:

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

三、增(Insert)

直接使用 UserInfo 对象的属性名来获取参数,注意绑定的参数要和 UserInfo 中的字段名相同

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);

返回插入数据的主键: @Options

Insert 语句默认返回的是受影响的行数。但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入数据的 id

比如订单系统:当我们下完订单之后,需要通知物流系统,库存系统,结算系统等,这时候就需要拿到订单ID。

如果想要拿到自增 id,需要在 Mapper 接口的方法上添加一个 Options 的注解:

java 复制代码
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into user_info (username, age, gender, phone) values (#{userInfo.username},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
Integer insert(@Param("userInfo") UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使用 JDBCgetGeneratedKeys 方法来取出由数据库内部生成的主键,默认是 false
  • keyProperty:指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert语句的 selectKey 子元素设置它的值,默认是未设置。

测试代码:

java 复制代码
@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()); // 从对象中取出id
}

// 结果:
添加数据条数:1,数据ID:6

注意: 设置 useGeneratedKeys=true 之后,方法返回值依然是受影响的行数,只不过自增 id 会设置在上述 keyProperty 指定的属性中,像上述代码一样从对象中取出 id 即可。

四、删(Delete)

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

五、改(Update)

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

六、查(Select)

在查询时发现,有几个字段是没有赋值的,只有 Java 对象属性和数据库字段一模一样时,才会进行赋值。

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

查询结果:

从运行结果上可以看到 SQL 语句中,查询了 delete_flagcreate_timeupdate_time,但是这几个属性却没有赋值。

注意: 如果 MySQL 查询返回多条,但是方法使用 UserInfo 接收而不是用 List<UserInfo> 接收的话,则 MyBatis 执行就会报错

原因分析:

当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写),所以上面就是出现了列名和字段名不同,导致赋值错误的情况。

解决办法:

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

1. 起别名

在 SQL 语句中,使用 as 给列名起别名,保持别名和实体类属性名一样:

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();

tip:SQL语句太长时,使用加号 + 进行字符串拼接。

2. 结果映射:@Results && @Result && @ResultMap

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from 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 定义一个 id ,然后其它 SQL 使用 @ResultMap 注解引入那个 id 即可,如下所示:

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
@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 user_info where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);

3. 开启驼峰命名(⭐⭐⭐推荐)

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而 Java 属性一般遵循驼峰命名法约定。

为了在这两种命名方式之间启用自动映射,需要在配置文件中将 mapUnderscoreToCamelCase 设置为 true

yaml 复制代码
mybatis:
  configuration:
    map-underscore-to-camel-case: true  #配置驼峰自动转换

驼峰命名规则:abc_xyz => abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz

Ⅳ. MyBatis XML配置文件开发

使用 Mybatis 的注解方式,主要是来完成一些简单的增删改查功能。如果需要实现复杂的 SQL 功能,建议使用 XML 来配置映射语句,也就是 SQL 语句写在 XML 配置文件中

MyBatis XML 的方式需要以下两步:

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

一、配置连接字符串和MyBatis

此步骤需要进行两项设置,数据库连接字符串设置和 MyBatis 的 XML 文件配置。

如果是 application.yml 文件,配置内容如下:

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
# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml

二、写持久层代码

持久层代码分两部分:

  1. 方法定义 Interface
  2. 方法实现:XXX.xml
1. 添加 mapper 接口

数据持久层的接口定义:

java 复制代码
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface UserInfoXMlMapper {
    List<UserInfo> queryAllUser();
}
2. 编写 UserInfoXMLMapper.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.demo.mapper.UserInfoXMlMapper">
    <select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
        select username,`password`, age, gender, phone from user_info
    </select>
</mapper>
  • <mapper> 标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接口的全限定名,包括全包名.类名。
  • <select> 查询标签:是用来执行数据库的查询操作的:
    • id:是和 Interface (接口)中定义的方法名称一样的,表示对接口的具体实现方法。
    • resultType:是返回的数据类型,也就是开头我们定义的实体类。

三、增删改查操作

1. 增(Insert)

UserInfoMapper 接口:

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

UserInfoMapper.xml 实现:

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

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

UserInfoMapper接口:

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

UserInfoMapper.xml实现:

xml 复制代码
<insert id="insertUser">
    insert into user_info (username, `password`, age, gender, phone) 
    values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})
</insert>

返回自增 id:接口定义不变,Mapper.xml 实现设置 useGeneratedKeyskeyProperty 属性,如下所示:

xml 复制代码
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user_info (username, `password`, age, gender, phone) 
    values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})
</insert>
2. 删(Delete)

UserInfoMapper接口:

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

UserInfoMapper.xml实现:

xml 复制代码
<delete id="deleteUser">
    delete from user_info where id = #{id}
</delete>
3. 改(Update)

UserInfoMapper接口:

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

UserInfoMapper.xml实现:

xml 复制代码
<update id="updateUser">
    update user_info set username=#{username} where id=#{id}
</update>
4. 查(Select)

同样的,使用 XML 的方式进行查询,也存在数据封装的问题:

xml 复制代码
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
    select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from user_info
</select>

结果显示:deleteFlag,createTime,updateTime 也没有进行赋值。

解决办法和注解类似:

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

其中方法 1 和方法 3 的解决办法和注解一样,不再多说,接下来看下 xml 如果来写结果映射:

Mapper.xml:

xml 复制代码
<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 user_info
</select>

Ⅴ. 多表查询

多表查询和单表查询类似,只是 SQL 不同而已。

Model:

java 复制代码
import lombok.Data;
import java.util.Date;

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

需求:根据 uid 查询作者的名称等相关信息

SQL 如下所示:

sql 复制代码
SELECT
    ta.id,
    ta.title,
    ta.content,
    ta.uid,
    tb.username,
    tb.age,
    tb.gender
FROM
    articleinfo ta
LEFT JOIN user_info tb ON ta.uid = tb.id
WHERE
    ta.id =1

此时就需要补充实体类:

java 复制代码
@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    
    // 添加用户相关信息
    private String username;
    private Integer age;
    private Integer gender;
}

接口定义:

java 复制代码
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ArticleInfoMapper {
    @Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +
           "FROM articleinfo ta LEFT JOIN user_info tb ON ta.uid = tb.id " +
           "WHERE ta.id = #{id}")
    ArticleInfo queryUserByUid(Integer id);
}

Ⅵ. #{}${}

MyBatis参数赋值有两种方式,前面使用了 #{} 进行赋值,接下来我们看下二者的区别。

  • #{}预编译处理 ,防止 SQL 注入,推荐使用
  • ${}字符串拼接,动态拼接 SQL,需谨慎使用。

此外,因为预编译 SQL 在编译一次之后会被缓存起来,后面再次执行这条语句时不会再编译,省去了解析优化等过程,提高了效率!

但是一些场景,#{} 不能处理,比如排序功能,表名,字段名作为参数时,这些情况需要使用 ${}

模糊查询虽然 ${} 可以完成,但因为存在SQL注入的问题,所以通常使用 mysql 内置函数 concat 来完成。

Ⅶ. 数据库连接池

常见的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

目前比较流行的是Hikari,Druid。

  1. Hikari:SpringBoot 默认使用的数据库连接池。Hikari是日语"光"的意思(ひかり),Hikari也是以追求性能极致为目标。

  2. Druid:如果我们想把默认的数据库连接池切换为 Druid 数据库连接池,只需要引入相关依赖即可:

    xml 复制代码
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-3-starter</artifactId>
          <version>1.2.21</version>
      </dependency>

如果 SpringBoot 版本为 2.X,使用 druid-spring-boot-starter 依赖:

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

参考官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

二者对比,参考:Hikaricp和Druid对比_数据库_晚风暖-华为云开发者联盟

相关推荐
老华带你飞2 小时前
志愿者服务管理|基于springboot 志愿者服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
William_cl3 小时前
【CSDN 精品专栏】ASP.NET Razor 变量输出 @变量名:从入门到避坑,新手也能写对!
java·数据库·asp.net
困死了11113 小时前
PostgreSQL笔记
数据库·笔记·postgresql
尤物程序猿3 小时前
spring的监听器的几种使用方式
java·数据库·spring
老华带你飞3 小时前
学生请假管理|基于springboot 学生请假管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·spring
一 乐3 小时前
校务管理|基于springboot + vueOA校务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
太行山有西瓜汁4 小时前
达梦DTS工具:批量导出与导入DDL脚本完整指南
运维·服务器·数据库
无盐海4 小时前
Redis 哨兵模式
数据库·redis·缓存
APItesterCris4 小时前
高并发场景下的挑战:1688 商品 API 的流量控制、缓存策略与异步处理方案
大数据·开发语言·数据库·缓存