MyBatis入门:快速搭建数据库操作框架 + 两种增删改查的方法(CRUD)

发布于:2025-05-27 ⋅ 阅读:(34) ⋅ 点赞:(0)

一、创建Mybatis的项目

Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在MySQL中操作的, 所以需要添加MySQL驱动

1.添加依赖

或者 手动添加依赖

        <!--Mybatis 依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.4</version>
        </dependency>
        <!--mysql驱动包-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

项⽬⼯程创建完成后,⾃动在pom.xml⽂件中,导⼊Mybatis依赖和MySQL驱动依赖

版本会随着SpringBoot 版本发⽣变化

SpringBoot 3.X对⽤MyBatis版本为3.X

对应关系参考:Introduction – mybatis-spring-boot-autoconfigure

2.配置常见的数据库信息

这是常见关于Mybatis的配置信息,大家可以自取

csdn暂时还不支持yml文件,这是 yml 文件:

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 保存路径
mybatis:  
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #自动驼峰转换

前提是有这样的一个数据库

那么这里提供一个数据库:

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
USE mybatis_test;

-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
        `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() ON UPDATE now(),
        PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );


-- 创建文章表
DROP TABLE IF EXISTS article_info;
        
CREATE TABLE article_info (
        id INT PRIMARY KEY auto_increment,
        title VARCHAR ( 100 ) NOT NULL,
        content TEXT NOT NULL,
        uid INT NOT NULL,
        delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        create_time DATETIME DEFAULT now(),
        update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';

-- 插入测试数据
INSERT INTO article_info ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );

这样一个表

3.写对应的对象

一般写在model文件夹下

@Data
public class UserInfo {

    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    //数据库用下划线连接单词,java直接用小驼峰
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

}

4.写持久层代码

一般写在mapper文件夹下,或者Dao

@Mapper
public interface UserInfoMapper {

    //查询所有用户的信息
    @Select("select * from user_info")
    List<UserInfo> selectAll();
}

5.单元测试

在对应的mapper接口下,右击

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectAll() {
        userInfoMapper.selectAll().forEach(x -> System.out.println(x));
        //等同于
//        List<UserInfo> userInfos = userInfoMapper.selectAll();
//        for (UserInfo userInfo : userInfos) {
//            System.out.println(userInfo);
//        }
    }
}

结果:

二、Mybatis的基本操作

1.日志打印

2.传递单个参数

通过 #{……} 传递 参数

@Mapper
public interface UserInfoMapper {
    
    //                 只有一个参数的时候这里写什么名字无所谓
    @Select("select * from user_info where id = #{id}")
    UserInfo selectUserById(Integer id);
}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectUserById() {
        System.out.println(userInfoMapper.selectUserById(2));
    }
}

结果:

3.传递多个参数

3种方法

方法1:

标签 和 方法 中的名字一样

方法2:

它给方法的每个形参取了别名,例如第一个param1 第二个 param2

方法3:

使用@Param("……"),和 标签中的 #{……}对应

@Mapper
public interface UserInfoMapper {

    //    @Select("select * from user_info where id = #{id} and gender = #{gender}")   方法1    推荐
    //    @Select("select * from user_info where id = #{param2} and gender = #{param1}")  方法2   不推荐
    //    @Select("select * from user_info where id = #{id2} and gender = #{gender2}")  错误
    //
    @Select("select * from user_info where id = #{id2} and gender = #{gender2}") // 方法3   推荐
    List<UserInfo> selectUserByIdAndGender(@Param("id2") Integer id,@Param("gender2") Integer gender);

}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectUserByIdAndGender() {
        List<UserInfo> userInfos = userInfoMapper.selectUserByIdAndGender(3, 1);
        System.out.println(userInfos);
    }
}

结果:

返回的参数可以是对象,也可以是集合类:

当我们知道数据库中对应的参数只有一个时,可以用类接收,但是最好用集合,万一他人增加了符号相同条件的数据,一个类就装不下。

4.查(Select)

查询之前已经都写到了,就不再写了。

发现这样的一个问题:

数据库的规范是单词之间用下划线分割,java的变量规范是小驼峰命名。这就导致了属性对应不上导致为null

解决办法,我先讲最推荐的:

4.1 开启驼峰命名

在yml或者properties文件中设置:

# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #自动驼峰转换

4.2 起别名

@Mapper
public interface UserInfoMapper {

    @Select("select id, username, password, age, gender, phone, delete_flag as deleteFlag, " +
            "create_time as createTime, update_time as updateTime from user_info")
    List<UserInfo> selectAll2();

}

即便是不用起别名也不建议用 select *就应该用到哪一列写哪一列,即便是所有的列都需要也这么写,因为更加规范。

4.3 结构映射

4.3.1 @Results 和 @Result
@Mapper
public interface UserInfoMapper {

//    @Select("select id, username, password, age, gender, phone, delete_flag as deleteFlag, " +
//            "create_time as createTime, update_time as updateTime from user_info")
    @Results({
        @Result(column = "delete_flag", property = "deleteFlag"),
        @Result(column = "create_time", property = "createTime"),
        @Result(column = "update_time", property = "updateTime")
    })
    @Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time from user_info")
    List<UserInfo> selectAll2();
}

这样比起别名麻烦呀,那么真实用法是这样:

4.3.2 @ResultMap

5.增(Insert)

5.1传递对象

方法一:

@Mapper
public interface UserInfoMapper {

    @Insert("insert into user_info (username, `password`, age, gender) values (#{username},#{password},#{age},#{gender})")
    Integer insertUser(UserInfo userInfo);

}

Mapper层

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("123445");
        userInfo.setAge(19);
        userInfo.setGender(0);

        userInfoMapper.insertUser(userInfo);
    }

}

成功:

方法二:

@Mapper
public interface UserInfoMapper {

    @Insert("insert into user_info (username, `password`, age, gender)" +
            "values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
    Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);
}
@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;


    @Test
    void insertUserByParam() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("李四");
        userInfo.setPassword("jaba213");
        userInfo.setAge(19);
        userInfo.setGender(0);

        userInfoMapper.insertUser(userInfo);
    }
}

6.删(Delete)

删除的时候一般使用id删除

假设我们现在是pdd的员工,我们下单了一个商品但是还没有付钱,那么此时我们就需要拿到这个订单的id,如果在10分钟内不付钱,我们就删除这个订单。

那么我们就需要在插入之后拿到id,可以用这个注解:

6.1 @Options

@Mapper
public interface UserInfoMapper {

    @Delete("delete from user_info where id = #{id}")
    Integer delete(Integer id);

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into user_info (username, `password`, age, gender)" +
            "values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
    Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);

}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void insertUserByParam() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("wangmaz");
        userInfo.setPassword("54231");
        userInfo.setAge(19);
        userInfo.setGender(0);
        //返回影响的行数
        Integer result = userInfoMapper.insertUserByParam(userInfo);
        //                                                  通过getId()获取            
        System.out.println("执行结果" + result + " ,id : " + userInfo.getId());
    }

结果:

那么拿到数据你想用这个id干什么自己处理就好了

普通的删除:

@Mapper
public interface UserInfoMapper {

    @Delete("delete from user_info where id = #{id}")
    Integer delete(Integer id);

}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void delete() {
//    删除id为11的数据
        userInfoMapper.delete(11);
    }
}

运行前:

运行后:

7.改(Update)

@Mapper
public interface UserInfoMapper {


    @Update("update user_info set password = #{password} where id = #{id}")
    Integer update(Integer id, String password);
}
@Mapper
public interface UserInfoMapper {

    @Update("update user_info set password = #{password} where id = #{id}")
    Integer update(Integer id, String password);
}

修改前:

修改后 :

三、报错信息

看到这样的报错“密码错误”,直接去看配置信息

数据库返回结构太多,方法定义的返回结果不匹配

标签参数和方法参数不匹配

四、Mybatis XML配置文件

💡

Mybatis的开发有两种⽅式:

1.注解

2.XML

💡

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

1. 配置数据库连接字符串和MyBatis

2. 写持久层代码

4.1配置连接字符串和MyBatis

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:  # 设置 Mybatis 的 xml 保存路径
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #自动驼峰转换

4.2 写持久层代码

💡

持久层代码分两部分

1. ⽅法定义 Interface

2. ⽅法实现: XXX.xml

4.2.1 添加mapper 接口 和 XML文件

添加接口:

package com.Li.mybatis.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface 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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
 
</mapper>

4.2.3 文件路径的存放位置

XML文件路径是配置了的需要和配置信息一样:

mybatis:  # 设置 Mybatis 的 xml 保存路径
  mapper-locations: classpath:mapper/*Mapper.xml

文件的存放位置:

4.2.4 如何使用

mapper:

@Mapper
public interface UserInfoXMLMapper {

    List<UserInfo> selectAll();

}

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.Li.mybatis.demo.mapper.UserInfoXMLMapper">

<!--         对应的方法名                  返回的类型路劲 -->
    <select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
        select * from user_info;
    </select>

</mapper>

💡

以下是对以上标签的说明:

  • <mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定名,包括全包名.类名。
  • <select> 查询标签:是⽤来执⾏数据库的查询操作的:
    • id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
    • resultType :是返回的数据类型,也就是开头我们定义的实体类。

单元测试:

@SpringBootTest
class UserInfoXMLMapperTest {

    @Autowired
    UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void selectAll() {
        userInfoXMLMapper.selectAll().forEach(x -> System.out.println(x));
    }
}

结果:

那么这种方法也纯在数据库名和java中名字不对应的情况。

4.3 查(Select)

解决办法和注解类似:

4.3.1 起别名

<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">

<!--         对应的方法名                  返回的类型路劲 -->
    <select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag as deleteFlag,
        create_time as createTime,
        update_time as updateTime
        from user_info;
    </select>

</mapper>

在xml文件中改sql语句就可以了:

4.3.2 结果映射

<mapper namespace="com.Li.mybatis.demo.mapper.UserInfoXMLMapper">

    <resultMap id="BaseMap" type="com.Li.mybatis.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>
<!--         注意讲这里的属性改为resultMap,并且指定id   -->
    <select id="selectAll" resultMap="BaseMap">
        select id, username, password, age, gender, phone,
        delete_flag,
        create_time,
        update_time
        from user_info;
    </select>

</mapper>

结果:

如果你又写了别名又写了映射,它就分不清哪个起作用,最后都没有作用,而且还不会报错:

4.3.3 开启驼峰命明

mybatis:
  configuration:
    map-underscore-to-camel-case: true  #自动驼峰转换
<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
    <!--         对应的方法名                  返回的类型路劲 -->
    <select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag,
        create_time,
        update_time
        from user_info;
    </select>
</mapper>

结果:

4.4 增(Insert)

方法一

写接口:

@Mapper
public interface UserInfoXMLMapper {

    Integer insert(UserInfo userInfo);
}

写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">


    <insert id="insert">
        insert into user_info (username, password, age, gender)
        value (#{username},#{password},#{age},#{gender})
    </insert>
</mapper>

单元测试:

@SpringBootTest
class UserInfoXMLMapperTest {

    @Autowired
    UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void insert() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("12314");
        userInfo.setAge(18);
        userInfo.setGender(0);

        userInfoXMLMapper.insert(userInfo);
    }
}

结果:

插入依然

<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
    
    <insert id="insert2">
        insert into user_info (username, password, age, gender)
        value (#{userInfo.username},#{userInfo.password},
        #{userInfo.age},#{userInfo.gender})
    </insert>
</mapper>

方法二:

写mapper:

@Mapper
public interface UserInfoXMLMapper {

    Integer insert2(@Param("userInfo") UserInfo userInfo);
}

写XML文件:

<mapper namespace="com.Li.mybatis.demo.mapper.UserInfoXMLMapper">

    <insert id="insert2">
        insert into user_info (username, password, age, gender)
        value (#{userInfo.username},#{userInfo.password},
        #{userInfo.age},#{userInfo.gender})
    </insert>
</mapper>

结果:

拿取插上数据的指定值:

<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!--                                   拿取插入数据的id值 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into user_info (username, password, age, gender)
        value (#{username},#{password},#{age},#{gender})
    </insert>
</mapper>

测试用例:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void insertUserByParam() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("wangmaz");
        userInfo.setPassword("54231");
        userInfo.setAge(19);
        userInfo.setGender(0);
        //返回影响的行数
        Integer result = userInfoMapper.insertUserByParam(userInfo);
        //                                                  通过getId()获取
        System.out.println("执行结果" + result + " ,id : " + userInfo.getId());
    }
}

结果:

4.5 删(Delete)

写mapper

@Mapper
public interface UserInfoXMLMapper {

    Integer deleteById(Integer id);
}

写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">

    <delete id="deleteById">
        delete from user_info where id = #{id}
    </delete>
</mapper>

测试用例:

@SpringBootTest
class UserInfoXMLMapperTest {

    @Autowired
    UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void deleteById() {
        userInfoXMLMapper.deleteById(15);
    }
}

删除前:

删除后:

4.6 改(UPdate)

写mapper:

@Mapper
public interface UserInfoXMLMapper {

    Integer updateById(Integer id,String username);
}

写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">

    <update id="updateById">
        update user_info set username = #{username} where id = #{id}
    </update>
</mapper>

单元测试:

@SpringBootTest
class UserInfoXMLMapperTest {

    @Autowired
    UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void updateById() {
        userInfoXMLMapper.updateById(14,"王五");
    }
}

执行前:

执行后:

五、其他查询操作

5.1 多表查询

正在使用的表结构:

新增这样的一张表:

那么我们现在要求显示article_info的所有字段,加上user_info的password和phone字段:

在mapper中写对应的sql语句:

@Mapper
public interface ArticleInfoMapper {

    @Select("select ta.id,ta.title,ta.content,ta.uid,ta.delete_flag,ta.create_time,ta.update_time," +
            "tb.`password`,tb.phone from article_info as ta " +
            "left join user_info as tb on ta.id = tb.id where ta.id = #{id}")
    List<ArticleInfo> selectArticleInfoAndUserInfo(Integer id);
}

💡

因为增加了两条不属于article_info表的信息,我们对应接收的类不存在这样的信息,应该怎么办?

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 password;
    private String phone;

}

单元测试:

@SpringBootTest
class ArticleInfoMapperTest {

    @Autowired
    private ArticleInfoMapper articleInfoMapper;

    @Test
    void selectArticleInfoAndUserInfo() {
        List<ArticleInfo> articleInfos = articleInfoMapper.selectArticleInfoAndUserInfo(1);
        articleInfos.forEach(x -> System.out.println(x));
    }
}

结果:

不建议使用多表查询

5.2 #{} 和 ${}

#{}和${}都可以用于赋值,那么它们肯定是有差异的,接下来我们通过案例分析

插入Integer数据

#{}->

@Mapper
public interface UserInfoMapper {
    @Select("select id, username, password, age, gender, phone, delete_flag, " +
            "create_time, update_time from user_info where id = #{id}")
    UserInfo selectUserById(Integer id);
}

${}->

@Mapper
public interface UserInfoMapper {
    @Select("select id, username, password, age, gender, phone, delete_flag, " +
            "create_time, update_time from user_info where id = ${id}")
    UserInfo selectUserById(Integer id);
}

$是参数直接替换

再来看看赋值为插入String的数据:

#{}->

@Mapper
public interface UserInfoMapper {

    @Select("select id, username, password, age, gender, phone, delete_flag, " +
            "create_time, update_time from user_info where username = #{name}")
    List<UserInfo> selectByName(String name);
}
@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectByName() {
        List<UserInfo> userInfo = userInfoMapper.selectByName("admin");
        System.out.println(userInfo);
    }
}

结果:

${}->

@Mapper
public interface UserInfoMapper {

    @Select("select id, username, password, age, gender, phone, delete_flag, " +
            "create_time, update_time from user_info where username = ${name}")
    List<UserInfo> selectByName(String name);
}

那么我们把admin加上单引号就可以了,那么也发现$比较麻烦

结果:

那么如果是直接替换,会出现一个非常严重的问题!

5.2.1 sql注入问题

那么我输入这样的一个字符串 《'or 1 = '1》

结果:

由于1 = '1' 恒为真,就拿到了所有用户的信息,那不就完了嘛,还有更加严重的sql注入

所以在使用$的时候一定要考虑到sql注入的问题,那么我改为#{}还会出现sql注入问题吗?

结果:

使用#就不会存在sql注入的问题

5.2.2性能更高

绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐

如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要

经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.

预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译

(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率

5.3 排序功能

那么我们来写代码:

使用#{}的情况:

@Mapper
public interface UserInfoMapper {

    //用参数控制是降序还是升序
    @Select("SELECT * FROM user_info ORDER BY id #{order}")
    List<UserInfo> selectByOrder(String order);
}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectByOrder() {
        userInfoMapper.selectByOrder("desc");
    }
}

结果:

它说我有sql语句错误,那么仔细看,它给desc加上了单引号,也就是这样:

💡

#{}在参数为String类型的时候会自动加上单引号,那么我们在使用这种关键字的时候就不适合使用#{}了,这个适合使用${}直接替换就非常合适了

我们说在使用${}的适合要考虑SQL注入的问题,那么这个也是纯在问题的:

那么这个时候我们就不允许用户输入,让用户选择,就避免了sql注入问题

5.4 like 查询

实现这样的语句

@Mapper
public interface UserInfoMapper {

    @Select("SELECT * FROM user_info WHERE username LIKE '%#{src}%'")
    List<UserInfo> selectByLike(String src);
}

单元测试:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;
    
    @Test
    void selectByLike() {
        userInfoMapper.selectByLike("wang");
    }
}

结果:

仍然是sql语句问题

之前说过#{}参数为String的时候会自动加上单引号,那么就变成了这样:

使用${}结果:

为了防止sql注入,可以使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

concat()可以拼接多个字符串,你可以根据自己的需求写多个,具体实现:

@Mapper
public interface UserInfoMapper {
    @Select("SELECT * FROM user_info WHERE username LIKE concat('%',#{src},'%')")
    List<UserInfo> selectByLike(String src);
}

结果:

这个时候刚好就可以使用#{},反正它会自己加单引号

六、数据库连接池

在上⾯Mybatis的讲解中, 我们使⽤了数据库连接池技术, 避免频繁的创建连接, 销毁连接

下⾯我们来了解下数据库连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,

⽽不是再重新建⽴⼀个.

💡

没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源

使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池.

优点:

  1. 减少了⽹络开销
  2. 资源重
  3. 提升了系统的性能

常⻅的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

⽬前⽐较流⾏的是 Hikari, Druid

1.Hikari : SpringBoot默认使⽤的数据库连接池

从这里也可以看出来

2.Druid

中文命:德鲁伊

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

<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 依赖:

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-starter</artifactId>
     <version>1.1.17</version>
</dependency>

七、 总结

7.1 MySQL 开发企业规范

1.表名, 字段名使⽤⼩写字⺟或数字, 单词之间以下划线分割. 尽量避免出现数字开头或者两个下划线中间只出现数字. 数据库字段名的修改代价很⼤, 所以字段名称需要慎重考虑。

MySQL 在 Windows 下不区分⼤⼩写, 但在 Linux 下默认是区分⼤⼩写. 因此, 数据库名, 表名, 字

段名都不允许出现任何⼤写字⺟, 避免节外⽣枝

正例: aliyun_admin, rdc_config, level3_name

反例: AliyunAdmin, rdcConfig, level_3_name

2.表必备三字段: id, create_time, update_time

id 必为主键, 类型为 bigint unsigned, 单表时⾃增, 步⻓为 1

create_time, update_time 的类型均为 datetime 类型, create_time表⽰创建时间,

update_time表⽰更新时间

有同等含义的字段即可, 字段名不做强制要求

3.在表查询中, 避免使⽤ * 作为查询的字段列表, 标明需要哪些字段(课堂上给⼤家演⽰除外).

  • 增加查询分析器解析成本
  • 增减字段容易与 resultMap 配置不⼀致
  • ⽆⽤字段增加⽹络消耗, 尤其是 text 类型的字段

7.2 #{} 和${} 区别

1. #{}:预编译处理, ${}:字符直接替换

2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险,使用是需要考虑sql注入防范

3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}

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


网站公告

今日签到

点亮在社区的每一天
去签到