03、使用MyBatis完成CRUD

一、背景

1、 什么是CRUD?;

  • 通常我们用CRUD代表增删改查四种操作。
  • C:Create、R:Relative、U:Update、D:Delete

2、 在本篇内容学习之前,需要一些准备工作:;

  • 创建一个新的模块【利用Maven构建的普通Java模块】

  • 修改 pom.xml 中的一些配置信息

  • 添加一条语句,将打包方式定义为 jar 包 【因为我们当前不需要在web服务器上运行我们的项目】

  • 添加 mybatis、mysql驱动、junit、logback的依赖

    <!--打了一个jar包-->
    <packaging>jar</packaging>
    <dependencies>
        <!--mybatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.11</version>
            <scope>import</scope>
        </dependency>

        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
            <scope>import</scope>
        </dependency>

        <!--添加junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!--导入日志文件的依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • mybatis-config.xml、logback.xml 文件 放到根路径下

(1)mybatis-config.xml 文件的配置信息如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--日志配置-->
    <settings>
        <setting name="logImpl" value="SLF4J"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--配置自己的数据库端口号和数据库-->
                <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                <!--配置自己的数据库用户名和密码-->
                <property name="username" value="root"/>
                <property name="password" value="111111"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--sql映射文件创建好之后,需要将该文件路径配置到这里[对应的Mapper文件]-->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

(2)logback.xml 文件的配置信息如下

<?xml version="1.0" encoding="UTF-8"?>

<configuration debug="false">
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{

     yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{

     50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${

     LOG_HOME}/TestWeb.log.%d{

     yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{

     yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{

     50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!--mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>

</configuration>
  • 提供com.powernode.mybatis.utils.SqlSessionUtil工具类【方便创建会话】
package com.powernode.mybatis.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

/**
 * @author Bonbons
 * @version 1.0
 */
public class SqlSessionUtil {

    private SqlSessionUtil(){

     }

    //定义一个SqlSession
    private static final SqlSessionFactory sqlSessionFactory;
    //在类加载的时候初始化SqlSessionFactory
    static {

        try {

            //此处自己的 mybatis-config.xml 文件位置要配置正确
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }
    //通过一个公有的方法为外部提供会话的对象
    public static SqlSession openSession(){

        return sqlSessionFactory.openSession(true);
    }
}
  • 最后一步就是创建测试用例

二、Insert【添加数据】

1、 前情回顾:;

  • 之前我们的 CarMapper.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">

<!--namespace先随便写-->
<mapper namespace="car">
    <insert id="insertCar">
        insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
    </insert>
</mapper>

插入数据的语句是固定的,如果想插入不同的数据,只能在这块自己调整参数 >> 很不方便

2、 改进方法1:;

(1)在MyBatis中可以将数据放到Map集合中,在使用Java程序处理业务时,将Map对象作为参数传递进来。
(2)在sql语句中使用 #{map集合的key}来完成传值,其中的 #{} 就是占位符
(3)给出改进后的 CarMapper.xml 中的配置信息【可以看出此时占位符中的参数已经替换为Map实例的key】

<?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">
<!--namespace先随意写一个-->
<mapper namespace="com.powernode.CarMapperTest">
    <!--insert sql:保存一个汽车信息-->
    <insert id="insertCar">
        insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{

     car_num},#{

     brand},#{

     guide_price},#{

     produce_time},#{

     car_type})
    </insert>
</mapper>

(4)再看一下测试用例中的代码:

@Test
public void testInsertCar(){

    //利用工具类创建会话
    SqlSession session = SqlSessionUtil.openSession();
    //创建一个Map存储我们要传递的信息
    Map<String, Object> map = new HashMap<String, Object>();
    // id,car_num,brand,guide_price,produce_time,car_type
    // 对于key的选取应该见名知意
    map.put("car_num", 3);
    map.put("brand", "奥迪");
    map.put("guide_price", 50.0);
    map.put("produce_time", "2022-10-18");
    map.put("car_type", "燃油车");
    //调用我们的添加方法
    session.insert("insertCar", map);
    //提交事务
    session.commit();
    session.close();
}

(5)运行后可以刷新 Navicat 查看结果
&nbsp;
3、 改进方法2:;
(1)设置一个普通的Java类,类的属性对应数据库中的字段,为外部提供GetSet方法【该系列将这种普通Java类称之为pojo

(2)创建一个 com.powernode.pojo.Car 类,用于封装数据

package com.powernode.mybatis.pojo;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Car {

    // id,car_num,brand,guide_price,produce_time,car_type
    // 使用包装类,出现null时不会报错
    // 对于这些私有属性要与表中数据对应上
    private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;
    //提供构造方法
    public Car(){

     }

    public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {

        this.id = id;
        this.carNum = carNum;
        this.brand = brand;
        this.guidePrice = guidePrice;
        this.produceTime = produceTime;
        this.carType = carType;
    }
    //提供get和set方法

    public Long getId() {

        return id;
    }

    public void setId(Long id) {

        this.id = id;
    }

    public String getCarNum() {

        return carNum;
    }

    public void setCarNum(String carNum) {

        this.carNum = carNum;
    }

    public String getBrand() {

        return brand;
    }

    public void setBrand(String brand) {

        this.brand = brand;
    }

    public Double getGuidePrice() {

        return guidePrice;
    }

    public void setGuidePrice(Double guidePrice) {

        this.guidePrice = guidePrice;
    }

    public String getProduceTime() {

        return produceTime;
    }

    public void setProduceTime(String produceTime) {

        this.produceTime = produceTime;
    }

    public String getCarType() {

        return carType;
    }

    public void setCarType(String carType) {

        this.carType = carType;
    }
    //重写toString方法

    @Override
    public String toString() {

        return "Car{" +
                "id=" + id +
                ", carNum='" + carNum + '\'' +
                ", brand='" + brand + '\'' +
                ", guidePrice=" + guidePrice +
                ", produceTime='" + produceTime + '\'' +
                ", carType='" + carType + '\'' +
                '}';
    }
}

(2)要修改 CarMapper.xml 文件,更改 insert 方法中占位符中的内容

  • 这里面需要注意一个问题,占位符中的内容并不一定是Car类中的参数,它这个对应机制是这样的:

#{parameter} --> getParemeter() 【也就是说会去Car类中寻找这个get方法来获取参数】

<?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">
<!--namespace先随意写一个-->
<mapper namespace="com.powernode.CarMapperTest">
    <!--insert sql:保存一个汽车信息-->
    <insert id="insertCar">
        insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{

     carNum},#{

     brand},#{

     guidePrice},#{

     produceTime},#{

     carType})
    </insert>
</mapper>

(3)原来的测试类中添加相关的的测试方法

@Test
public void testInsertCarByPOJO(){

    SqlSession session = SqlSessionUtil.openSession();
    //封装数据
    Car car = new Car(null, "4", "托马斯", 200.0, "2022-11-11","煤油车");
    //事务处理
    session.insert("insertCar", car);
    //提交事务
    session.commit();
    session.close();
}

4、 总结;

  • 如果采用map集合传参,#{} 里写的是map集合的key,如果key不存在不会报错,数据库表中会插入NULL
  • 如果采用POJO传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写【getAge对应的是 #{age}

三、delete 和 update【删除、更新数据】

  • 此处我们只涉及简单的删除和更新操作,重点在于了解基本语法
  • 我们根据 id 来完成数据的删除和更新
  • 此部分给出在 CarMapper.xml 中的配置信息以及测试类中的对应测试方法

1、 通过指定id删除数据;
(1)在CarMapper.xml 中的配置信息如下【为了看起来更简洁,只给出对应的SQL语句】

  • 有一件值得注意的事情:当只存在一个占位符时,可以自动识别,参数名不匹配也没有问题
<!--根据id删除数据-->
<delete id="deleteById">
    delete from t_car where id = #{

     id}
</delete>

它的外部代码是这样的

<?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="balabala">
<!--在这部分写我们的SQL语句-->
</mapper>

(2)测试方法如下【也是只给出了完整方法,没去写类名】

@Test
public void testDeleteCarById(){

    SqlSession sqlSession = SqlSessionUtil.openSession();
    // count会记录操作实际影响数据库表中数据的条数
    int count = sqlSession.delete("deleteById", 15);
    //通过变量名.sout 可以快速生成输出指定变量的语句[在IDEA中]
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

(3)成功运行,删除了原来 id = 15 的数据
&nbsp;
2、 通过指定id更新数据;
(1)在CarMapper.xml 中的配置信息如下

<!--根据id修改数据-->
<update id="updateById">
    update t_car set car_num = #{

     carNum}, brand = #{

     brand}, guide_price = #{

     guidePrice}, produce_time = #{

     produceTime},car_type = #{

     carType} where id = #{

     id}
</update>

(2)测试方法如下

@Test
public void testUpdateCarById(){

    SqlSession sqlSession = SqlSessionUtil.openSession();
    Car car = new Car(17L, "1111", "奔驰大G", 200.0, "2020-01-01", "汽油越野车");
    int count = sqlSession.update("updateById", car);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

(3)成功运行,修改了原来 id = 17 的数据
&nbsp;


四、Select【查询数据】

  • 查询语句的重点在于:对于查询结果集的处理以及类属性名与数据库字段名的对应
  • 此处分别以查询一条数据和查询全部数据为例,来说明MyBatis中是如何完成数据查询的

1、 根据 id 查询一条数据:

(1)CarMapper中的配置信息

<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
    <!--select * from t_car where id = #{

     id}-->
    select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = #{

     id}
</select>

细心的盆友不难发现,查询语句与其他数据操作SQL语句不同:

第一,多了resultType;第二为查询的字段设置了别名

  • 使用 resultType 是为了告诉 MyBatis 我们查询结果集是一个什么类型的数据【使用的是全限定类名(类的完整地址)】
  • 设置别名,是因为如果类属性与数据库表中字段对应不上,就无法正确传递参数值

(2)测试方法

@Test
public void testSelectById(){

    SqlSession sqlSession = SqlSessionUtil.openSession();
    //用Car的实例接收返回值
    Car car = sqlSession.selectOne("selectById",1);
    //打印查询结果[之所以此处可以输出属性和值,是因为在Car类中重写了toString方法]
    System.out.println(car);
    //查询语句不需要提交事务
    sqlSession.close();
}

&nbsp;

2、 查询全部的数据;

  • 与前者的区别在于:查询时不需要设置条件、返回的是一个结果的集合

(1)CarMapper中的配置信息

<!--查询数据库表中全部数据-->
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
    select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car
</select>

(2)测试方法

@Test
public void testSelectAll(){

    SqlSession sqlSession = SqlSessionUtil.openSession();
    List<Car> cars = sqlSession.selectList("selectAll");
    //使用Lamda表达式+forEach输出
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}

&nbsp;
3、 namespace用法说明;

  • 当出现两个 XxxMapper.xml 文件中出现同IDSQL语句时,可能会出现问题
  • 我们可以通过 namespace.SQL语句id 来指定使用哪个Mapper中的 SQL 方法
  • 简言之,命名空间 >> 防止 id 冲突
  • PS:如果设置了多个Mapper.xml 文件,要记得添加到核心配置文件中