【JavaEE】万字详解Mybatis(上)

目录

什么是Mybatis?

Mybatis入门

1.准备工作

创建工程

数据准备

2.配置数据库连接字符串

3.写持久层代码

4.单元测试

补充:使用IDEA自动生成测试类

Mybatis的基础操作

打印日志

参数传递

增(Insert)

删(Delete)

改(Update)

查(select)


本文目标:

使用Mybatis完成简单的增删改查操作,参数传递

掌握Mybatis的写法一:注解(下篇文章讲解XML方式)

掌握Mybatis相关的日志配置

我们学习MySQL数据库时,已经学习了JDBC来操作数据库,但是JDBC操作太复杂了

JDBC操作示例回顾

我们先来回顾一下JDBC的操作流程:

  1. 创建数据库连接池DataSource
  2. 通过DataSource获取数据库连接Connection
  3. 编写要执行 带 ? 占位符的SQL语句
  4. 通过Connection及SQL创建操作命令对象Statement
  5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
  6. 使用Statement执行SQL语句
  7. 查询操作:返回结果集ResultSet,更新操作:返回更新的数量
  8. 处理结果集
  9. 释放资源

在正式学习前,我们先抛出个问题:

用JDBC/Mybatis操作数据库时,IDEA应该创建什么类型的项目?普通Java、Maven、Spring项目?

回答:

如果只是进行简单的JDBC操作,且不需要额外的功能或框架,可以创建普通的Java项目

如果希望使用Maven来管理依赖,简化构建过程,或者项目可能需要引入多个外部库,可以创建Maven项目

如果项目需要集成Spring框架的功能,或者预计未来会扩展为更复杂的项目,可以创建Spring项目

下面的一个完整案例,展示了通过JDBC的API向数据库中添加一条记录,修改一条记录,查询一条记录的操作。我们这里以创建Maven项目为例

数据库操作:

sql 复制代码
--创建数据库
Create database if not exists library default character set utf8mb4;
--使用数据库
Use library;
--创建表
Create table if not exists soft_bookrack(
Book_name varchar(32) NOT NULL,
Book_author varchar(32) NOT NULL,
Book_isbn varchar(32) NOT NULL primary key
);

以下是JDBC操作的具体实现代码:

java 复制代码
public class SimpleJdbcOperation {
    private final DataSource dataSource;

    public SimpleJdbcOperation(DataSource dataSource){
        this.dataSource=dataSource;
    }

    //添加一本书
    public void addBook(){
        Connection connection=null;
        PreparedStatement stmt=null;
        try{
            //获取数据库连接
            connection=dataSource.getConnection();
            //创建语句
            stmt=connection.prepareStatement(
                    "insert into soft_bookrack(book_name,book_author," +
                            "book_isbn) values(?,?,?)"
            );
            //参数绑定
            stmt.setString(1,"Spring in Action");
            stmt.setString(2,"Craig Walls");
            stmt.setString(3,"4931849231");
            //执行语句
            stmt.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //清理资源
            try{
                if(stmt!=null){
                    stmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //更新一本书
    public void updateBook(){
        Connection connection=null;
        PreparedStatement stmt=null;
        try{
            //获取数据库连接
            connection=dataSource.getConnection();
            //创建语句
            stmt=connection.prepareStatement(
                    "update soft_bookrack set book_author=? where book_isbn=?"
            );
            //参数绑定
            stmt.setString(1,"张伟");
            stmt.setString(2,"4931849231");
            //执行语句
            stmt.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //清理资源
            try{
                if(stmt!=null){
                    stmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //查询一本书
    public void queryBook(){
        Connection connection=null;
        PreparedStatement stmt=null;
        ResultSet rs=null;
        Book book=null;
        try{
            //获取数据库连接
            connection=dataSource.getConnection();
            //创建语句
            stmt=connection.prepareStatement(
                    "select * from soft_bookrack where book_isbn=?"
            );
            //参数绑定
            stmt.setString(1,"4931849231");
            //执行语句
            rs=stmt.executeQuery();
            if(rs.next()){
                book=new Book();
                book.setName(rs.getString("book_name"));
                book.setAuthor(rs.getString("book_author"));
                book.setIsbn(rs.getString("book_isbn"));
            }
            System.out.println(book);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //清理资源
            try{
                if(rs!=null){
                    rs.close();
                }
                if(stmt!=null){
                    stmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Book{
        private String name;
        private String author;
        private String isbn;
        //省略setter getter方法

        public void setName(String name) {
            this.name = name;
        }
        public void setAuthor(String author) {
            this.author = author;
        }
        public void setIsbn(String isbn) {
            this.isbn = isbn;
        }
    }
}

从上述代码和操作流程可以看出,对于JDBC来说,整个操作非常的繁琐,我们不但要拼接每一个参数,而且还要按照模板代码的方式,一步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写。那有没有一种方法,可以更简单、更方便的操作数据库呢?

答案是肯定的,这就是我们为什么要学习Mybatis的真正原因,它可以帮助我们更方便、更快速的操作数据库

什么是Mybatis?

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

Mybatis本是Apache的一个开源项目iBatis,2010年这个项目apache迁移到了google code,并且改名为Mybatis。2013年11月迁移到Github

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

简单来说,Mybatis是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具。接下来,我们就通过一个入门程序,让大家感受一下通过Mybatis如何来操作数据库

Mybatis入门

Mybatis操作数据库的步骤:

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

1.准备工作

创建工程

创建spring-boot工程,并导入mybatis的起步依赖、mysql的驱动包

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

项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖

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

SpringBoot3.X对应的Mybatis版本为3.X

对应关系可以参考:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

pom.xml文件如下:

XML 复制代码
<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>

数据准备

创建用户表,并创建对应的实体类User

sql 复制代码
-- 创建数据库
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' ); 

创建对应的实体类UserInfo

实体类的属性名与表中的字段名一一对应

关于这里的命名规则,我们后续会详细讲解

2.配置数据库连接字符串

Mybatis要连接数据库,需要数据库相关参数配置

·MySQL驱动类

·登录名(数据库)

·密码(数据库)

·数据库连接字符串

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

XML 复制代码
# 数据库连接配置
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  //MySQL驱动类

注意事项

如果使用MySQL是5.X之前的,使用的是"com.mysql.jdbc.Driver"。如果是大于5.x使用的是"com.mysql.cj.jdbc.Driver"

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

XML 复制代码
#驱动类名称 
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 
#数据库连接的url 
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test? 
characterEncoding=utf8&useSSL=false 
#连接数据库的⽤⼾名 
spring.datasource.username=root 
#连接数据库的密码 
spring.datasource.password=root

3.写持久层代码

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

java 复制代码
package com.fei.mybatisstudy.mapper;

import com.fei.mybatisstudy.model.UserInfo;
import org.apache.ibatis.annotations.*;


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

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

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

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

Mapper接口

需要明确的是,我们目前都是使用【**注解的方式】**进行数据库操作

在下文的【Mybatis XML配置文件】,我们会详细讲解【使用XML的方式】进行数据库操作

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

4.单元测试

在创建出来的SpringBoot工程中,在src下的test目录下,已经自动帮我们创建好了测试类,我们可以直接使用这个测试类来进行测试

只需要在contextLoads中补充需要测试的代码即可。如下图:

测试类上添加了注解@SpringBootTest,该测试类在运行时,就会自动加载Spring的运行环境。我们通过@Autowired这个注解,注入我们要测试的类,就可以开始测试了

运行结果如下:

SQL语句: @Select("select username,`password`,age,gender,phone from user_info")

返回结果中,我们可以看到,只有SQL语句中查询的列对应的属性才有赋值

在这里我们可以看到有些字段的值是null,之后会在 Select中单独讲解

补充:使用IDEA自动生成测试类

除此之外,我们也可以使用IDEA自动生成测试类

1.在需要测试的Mapper接口中,右键->Generate->Test

2.选择要测试的方法,点击即可

3.书写测试代码

我们只需要在自动生成的测试代码中,输入代码即可

注意:记得在测试类的头上加@SpringBootTest注解,来加载Spring运行环境

Mybatis的基础操作

上面我们学习了Mybatis的查询操作,接下来我们学习Mybatis的增删改查操作

在学习这些操作之前,我们先来学习Mybatis日志打印

打印日志

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

在配置文件(application.yml)中进行配置即可

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

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

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

注意:后续配置项,默认只提供一种,请大家自行进行(properties和yml)配置项转换

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

①:查询语句

②:传递参数及类型

③:SQL执行结果

参数传递

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

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

但是这样的话,只能查找id=4的数据,所以SQL语句中的id值不能写成固定数值,需要变为动态的数值

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

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

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

添加测试用例

运行结果:

增(Insert)

SQL语句:

sql 复制代码
insert into user_info (username, `password`, age, gender, phone) values 
("zhaoliu","zhaoliu",19,1,"18700001234")

把SQL中的常量替换为动态的参数

Mapper接口

我们这里直接使用UserInfo对象的属性名来获取参数

注意:如果SQL语句太长时,使用 【加号+】 进行字符串拼接

测试代码:

运行后,观察数据库执行结果

注意:如果这里设置了@Param属性,#{}需要使用 【对象.参数】来获取

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

返回主键

Insert语句默认返回的是 受影响的行数

但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入数据的id

比如订单系统

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

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

代码如下:

sql 复制代码
    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into user_info (username,`password`, age, gender, phone) " +
            "values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
    Integer insert2(@Param("userInfo") UserInfo userInfo);

测试代码

java 复制代码
 @Test
    void insert2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("z");
        userInfo.setPassword("zz");
        userInfo.setGender(2);
        userInfo.setAge(21);
        userInfo.setPhone("18612340005");
        Integer count = userInfoMapper.insert2(userInfo);
        System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());
    }

运行结果:

注意:设置useGeneratedKeys=true之后,方法返回值依然是受影响的行数,自增id会设置在上述keyProperty指定的参数中

删(Delete)

SQL语句:

sql 复制代码
delete from user_info where id=6

把SQL中的常量替换为动态的参数

Mapper接口

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

数据库数据如下:

我们现在删除id=4的数据

测试用例如下:

运行结果如下:

我们可以看到id=4的数据已经被删除了

改(Update)

SQL语句:

sql 复制代码
update user_info set username="zhaoliu" where id=5 

把SQL中的常量替换为动态的参数

Mapper接口:

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

测试用例:

修改id=11的用户名

运行结果:

查(select)

在单元测试中,我们发现查询结果中有些为null。如下图:

上述红色标记的语句 即是 SQL语句中所查询的列

我们在上面查询时发现,有几个字段是没有赋值的。只有Java对象属性和数据库字段一模一样时/只有SQL语句中查询的列对应的属性,才会进行赋值

接下来我们多查询一些数据

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

查询结果:

当我们的SQL语句中查询的列添加了id后,我们可以看到这里的id已经有值了。

但是绿色框框的deleteFlag、createTime、updateTime仍然为null

但是我们在SQL语句中查询的列有这三个字段,为什么仍然为null呢?

主要原因是:UserInfo中的属性名和数据库表的字段名不匹配(如下图)

结果映射、字段命名转换

补充说明:

Mybatis会根据【****方法的返回结果】****进行赋值

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

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

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

原因分析:

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

解决办法:

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

1.起别名

在SQL语句中,给列名(数据库表)起别名,保持别名和实体类属性名一样

java 复制代码
@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> queryAllUser3();

测试用例:

运行结果:

2.结果映射

java 复制代码
@Select("select * 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> queryAllUser4();

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

使用id属性给该Results定义别名,然后使用@ResultMap注解来复用

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

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

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

XML 复制代码
mybatis:
  configuration: # 配置打印 MyBatis⽇志
  mapper-locations: classpath:mapper/**.xml #XML文件配置

驼峰命名规则:abc_xyz==> abcXyz

数据库表中字段名:abc_xyz

实体类中属性名:abcXyz

大家可以看到,在讲这个之前我们的数据库表名和实体类属性名即符合上述命名规则,就是为了这里,此时我们只需要配置一下,Java代码不需要做任何处理,即可解决名称不一致问题

字段全部正确赋值

相关推荐
写代码的【黑咖啡】2 小时前
HiveSQL 语法详解与常用 SQL 写法实战
数据库·sql
我命由我123452 小时前
Android 开发 Room 数据库升级问题:A migration from 6 to 7 was required but not found.
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
黄筱筱筱筱筱筱筱2 小时前
7.适合新手小白学习Python的异常处理(Exception)
java·前端·数据库·python
怣502 小时前
MySQL WHERE子句完全指南:精准过滤数据的艺术
数据库·mysql
大鳥2 小时前
第一章 - 数据仓库是什么
大数据·数据库·hive
u0109272714 小时前
RESTful API设计最佳实践(Python版)
jvm·数据库·python
qq_1927798710 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
u01092727111 小时前
使用Plotly创建交互式图表
jvm·数据库·python
爱学习的阿磊11 小时前
Python GUI开发:Tkinter入门教程
jvm·数据库·python