MyBatis 入门

MyBatis

案例项目源码:https://gitee.com/pikachu2333/mybatis-simple

框架

软件开发中的框架

  • 框架是可被应用开发者定制的应用骨架
  • 框架是一种规则,保证开发者遵守相同的方式开发程序
  • 框架提倡“不要重复造轮子”,对基础功能进行封装

框架的优点

  • 极大的提高了开发的效率
  • 统一的编码规则,利于团队管理
  • 灵活配置的应用,拥有更好的维护性

SSM开发框架

  • Spring 对象容器框架,提供底层的对象管理,是框架的框架,其他的框架都要基于该框架进行开发。
  • Spring MVC web开发框架,用于替代servlet,提供Web底层的交互,进行更有效的web开发。
  • MyBatis 数据库框架,用于简化数据库操作,对JDBC进行了封装及扩展,提供了数据库的增删改查的便捷操作

MyBatis介绍

https://mybatis.org/mybatis-3/zh/index.html

  • MyBatis是优秀的持久层框架,将内存中的数据保存在数据库中防止重启后数据丢失
  • MyBatis使用XML将SQL与程序解耦,便于维护
  • MyBatis学习简单,执行高效,是JDBC的延伸

MyBatis开发流程

  • 引入MyBatis依赖
  • 创建核心配置文件
  • 创建实体(Entity)类
  • 创建Mapper映射文件
  • 初始化SessionFactory
  • 利用SqlSession对象操作数据

准备知识:单元测试与JUnit4

Junit目前已经更新到5 https://junit.org/junit5/

单元测试

  • 单元测试是指对软件中的最小可测试单元进行检查和验证
  • 测试用例是指编写一段代码对已有功能(方法)进行校验
  • JUnit 4是Java中最著名的单元测试工具,主流IDE内置支持

JUnit 4使用方法

  • 引入JUnit Jar包或增加Maven依赖
  • 编写测试用例验证目标方法是否正确运行
  • 在测试用例上增加**@Test**注解开始单元测试

普通工程,引用hamcrest-core-1.3.jar底层依赖和junit-4.13.2.jar,比较麻烦,下面使用Maven创建

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

案例:计算器加减测试:

Calculate.java 计算器类

package junit;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }
}

CalcultorTest.java 计算器测试用例类

import junit.Calculator;
import org.junit.Test;

public class CalcultorTest {

    private Calculator cal = new Calculator();

    //测试用例方法命名一般两种规范
    //1. 与原方法保持一致
    //2. 在原方法前增加test前缀
    @Test
    public void add(){
        int result = cal.add(1, 2);
        System.out.println(result);
    }

    @Test
    public void subtract(){
        int result = cal.subtract(1, 2);
        System.out.println(result);
    }
}

MyBatis环境配置

新建案例项目

创建maven工程,名为:mybatis

pom.xml 配置引用jar包mybatis和mysql,还有为方便测试引入junit组件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public
            </url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

引用mysql数据,在idea中新建数据库连接,运行"babytun建库脚本.sql",此文件在gitee上传的源代码lib中有
在这里插入图片描述
mybatis使用xml保存配置信息,通常配置文件名叫:

mybatis-config.xml

  • Mybatis采用xml文件配置数据库环境信息
  • Mybatis环境配置标签<environment>
  • environment配置包含数据库驱动,URL,用户名和密码
    在这里插入图片描述

SqlSessionFactory

SqlSessionFactory是MyBatis中的一个重要的对象,它是用来创建SqlSession对象的,而SqlSession用来操作数据库的。

  • SqlSessionFactory是MyBatis的核心对象
  • SqlSessionFactory用于初始化MyBatis,读取配置文件。是一个工厂方法,用于创建SqlSession对象。
  • 要保证SqlSessionFactory在应用全局中只存在唯一的对象,通常会使用静态类的方式对其进行初始化。

SqlSession

SqlSession是MyBatis用来操作数据库的一个核心对象,不那么严谨的说,可以将SqlSession看做类似于之前学习过的JDBC的连接接口对象(Connection)执行接口对象(PreparedStatement)的组合,用来执行CRUD操作。

  • SqlSession是MyBatis操作数据库的核心对象
  • SqlSession使用JDBC的方式与数据库交互
  • SqlSession对象提供了数据表的CRUD方法

mybatis.MyBatisTestor.java 测试代码

package mybatis;

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 org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//junit单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //通过MyBatis提供的资源类,获取对应的配置文件作为字符流读取
        //getResourceAsReader方法会默认的从当前classpath类路径下加载文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        //初始化SqlSessionFactory,并同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        System.out.println("SqlSessionFactory对象加载成功");
        //创建SqlSession对象,用于与数据库产生交互,注意SqlSession它是JDBC的扩展类
/*
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //创建数据库连接(测试用,实际开发不需要创建)
        Connection connection = sqlSession.getConnection();
        System.out.println(connection);
*/
        SqlSession sqlSession = null;
        try {
            sqlSession = sqlSessionFactory.openSession();
            //在SqlSession对象底层存在Connection(java.sql)连接对象,可以通过getConnection方法得到该对象
            Connection connection = sqlSession.getConnection();
            //该connection对象的创建仅做演示测试用,在mybatis中,无需使用任何与JDBC有关的类
            System.out.println(connection);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(sqlSession != null) {
                //在mybatis-config.xml文件中,dataSource节点type属性:
                //如果type=“POOLED”,代表使用连接池,close则是将连接回收到连接池中
                //如果type=“UNPOOLED”,代表直连,close则会调用Connection.close()方法关闭连接
                //这是配置带来的底层处理机制的不同
                sqlSession.close();
            }
        }
    }
}

初始化工具类

初始化工具类保证SqlSessionFactory应用全局唯一

mybatis.utils.MyBatisUtils.java

package 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;
import java.io.Reader;

public class MyBatisUtils {
    //利用static关键字保证全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static {
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSqlSession() {
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的SqlSession对象
     * @param sqlSession 准备释放的SqlSession对象
     */
    public static void closeSqlSession(SqlSession sqlSession) {
        if(sqlSession != null) {
            sqlSession.close();
        }
    }
}

测试

    @Test
    public void testMyBatisUtils() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSqlSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        } catch (Exception e) {
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(sqlSession);
        }

    }

MyBatis数据查询步骤

  • 创建实体类(Entity)
  • 创建Mapper XML
  • 编写<select> SQL标签
  • 开启驼峰命名映射
  • 新增<mapper>
  • SqlSession执行select语句

1. 创建实体类(Entity)

main/java下创建mybatis.entity包,entity包下创建数据库中t_goods表对应的Goods商品实体类,将数据表中的字段对应在实体类中增加一系列的私有属性及getter/setter方法,属性采用驼峰命名。
在这里插入图片描述
mybatis.entity.Goods.java

package mybatis.entity;

public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Float getOriginalCost() {
        return originalCost;
    }

    public void setOriginalCost(Float originalCost) {
        this.originalCost = originalCost;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }

    public Float getDiscount() {
        return discount;
    }

    public void setDiscount(Float discount) {
        this.discount = discount;
    }

    public Integer getIsFreeDelivery() {
        return isFreeDelivery;
    }

    public void setIsFreeDelivery(Integer isFreeDelivery) {
        this.isFreeDelivery = isFreeDelivery;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }
}

2. 创建Mapper XML说明SQL语句

main/resources下创建新的子目录mappersmappers代表映射器,里面存放的都是xml文件。创建goods.xml文件来说明实体类和数据表的对应关系(和哪个数据表对应,属性和字段怎么对应)。

3. 在Mapper XML中增加SQL语句对应标签

  • 根节点通过增加不同的命名空间namespace来区分不同的mapper文件,通常我们会将针对一张表操作的SQL语句放置在一个mapper文件中。
  • 语句节点的id属性为别名,相当于SQL名称,同一个namespace下id要唯一,不同的namespace可以重名;因此namespace的设置就很有必要,不然调用SQL的时候分不清哪个id。
  • 语句节点的resultType属性代表返回的对象是什么,为实体类的完整路径,在SQL语句执行完后会自动的将得到的每一条记录包装成对应的实体类的对象。

goods.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 将之前的config DTD约束改为mapper DTD约束 -->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 为这个mapper指定一个唯一的namespace -->
<!-- 注意每个mapper文件的namespace是不能相同的 -->
<!-- namespace非常类似于Java类的包,同样也是用于区分每个不同的mapper文件 -->
<!-- 所以namespace的值,习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的 -->
<mapper namespace="goods">
    <!-- id属性相当于为SQL语句起了个名称 -->
    <!-- 在一个mapper文件中是不允许出现相同的id属性值的 -->
    <!-- resultType代表返回结果的类型,它会将SQL语句执行完的每一条结果包装为对应的属性值指定的对象 -->
    <select id="selectAll" resultType="mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10
    </select>
</mapper>

4. 在mybatis-config.xml中增加Mapper XML文件声明

mybatis-config.xml中添加mappers标签,这样MyBatis在初始化的时候才知道这个goods.xml的存在

mybatis-config.xml

    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>

5. 利用SqlSession执行Mapper XML中的SQL语句

    @Test
    public void testSelectAll() {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSqlSession();
            List<Goods> list = session.selectList("goods.selectAll");
            for (Goods goods : list) {
                System.out.println(goods.getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSqlSession(session);
        }

    }

获取到的数据只有title,存在数据丢失,因为我们的查询结果类型字段和表中字段名不能匹配!

6. 在mybatis-config.xml中开启驼峰命名映射

mybatis-config.xml

<settings>
    <!-- 驼峰命名转化设置 -->
    <!-- 该设置表示将数据库中表的字段,比如goods_id => goodsId -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

SQL传参

在数据操作节点中,可以添加parameterType属性指定参数类型,并采用#{param}的形式接受传入的参数。

good.xml

    <!-- 单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数-->
    <select id="selectById" parameterType="Integer" resultType="mybatis.entity.Goods">
        select * from t_goods where goods_id = #{value}
    </select>

    <!-- 多参数传递时,使用parameterType指定Map接口,SQL中#{key}提取参数 -->
    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="mybatis.entity.Goods">
    select * from t_goods
    where
    current_price between #{min} and #{max}
    order by current_price asc
    limit 0,#{limt}
    </select>

测试代码:

    @Test
    public void testSelectById() {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSqlSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.getTitle());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

    @Test
    public void testselectByPriceRange() {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSqlSession();
            Map param = new HashMap();
            //map中的key-value的key值,需要和数据操作节点中参数名一致
            param.put("min",100);
            param.put("max" , 500);
            param.put("limt" , 10);
            List<Goods> list = session.selectList("goods.selectByPriceRange", param);
            for (Goods goods : list) {
                System.out.println(goods.getTitle() + " : " + goods.getCurrentPrice());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

获取多表关联查询结果

将返回的结果变为Map类型,这样MyBatis就会将结果封装为Map集合中对应的键值对

    <select id="selectGoodsMap" resultType="java.util.Map" flushCache="true">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

测试代码:

    @Test
    public void testSelectGoodsMap() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSqlSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for(Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

该方法返回的结果为数据库中表对应的原始字段名为key值,而且查询到的结果的字段前后顺序是混乱的。为了保证我们等到的结果的顺序和数据库中的顺序一致,我们需要使用LinkedHashMap

  • LinkedHashMap是采用链表形式的HashMap,他在进行数据提取的时候是按照插入数据时的顺序进行提取保存的,不会出现乱序的情况。
  • 使用LinkedHashMap来接收数据是常用的,因为公司的数据结构较为复杂,需要多表关联查询,LinkedHashMap可以有效进行数据的扩展,非常灵活。
  • 缺点:太过灵活,任何查询结果都会被LinkedHashMap包装在内,相比较实体类而言,缺少了编译时检查,容易出错。

保证顺序:

    <select id="selectGoodsMap" resultType="java.util.Map" flushCache="true">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

灵活添加字段:

<!-- 新增一个test字段,结果test字段会被自动添加 -->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
    select g.* , c.category_name,'1' as test from t_goods g , t_category c
    where g.category_id = c.category_id
</select>

ResultMap结果映射

  • ResultMap可以将查询结果映射为复杂类型的Java对象。
  • ResultMap适用于Java对象保存多表关联结果
  • ResultMap是MyBatis关联的核心所在,支持对象关联查询等高级特性

mybatis包下面新建一个dto包,新建GoodsDTO类。

DTO是一个特殊的JavaBean,数据传输对象。对原始对象进行扩展,用于数据保存和传递。

GoodsDTO.java

package mybatis.dto;

import mybatis.entity.Goods;

/**
 * 扩展类,数据传输对象
 */
public class GoodsDTO {
    private Goods goods = new Goods();
    private String categoryName;
    private String test;

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}

使用resultMap属性,添加结果映射:

<!--结果映射,通常id属性设置为rm+...,type指明要转化为的DTO -->
<resultMap id="rmGoods" type="mybatis.dto.GoodsDTO">
    <!--设置主键字段与属性映射,必写-->
    <!-- property=goods.goodsId指的是,每次查到的goods_id字段的数据会为GoodsDTO类中goods属性对象的goodsId属性进行赋值 -->
    <id property="goods.goodsId" column="goods_id"></id>
    <!--设置非主键字段与属性映射-->
    <result property="goods.title" column="title"></result>
    <result property="goods.originalCost" column="original_cost"></result>
    <result property="goods.currentPrice" column="current_price"></result>
    <result property="goods.discount" column="discount"></result>
    <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
    <result property="goods.categoryId" column="category_id"></result>
    <result property="categoryName" column="category_name"></result>
    <result property="test" column="test"/>
</resultMap>
<select id="selectGoodsDTO" resultMap="rmGoods">
    select g.* , c.*,'1' as test from t_goods g , t_category c
    where g.category_id = c.category_id
</select>

测试:

    @Test
    public void testSelectGoodsDTO() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSqlSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list) {
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

其实我们可以继续扩展,比如我现在不仅仅想要得到category_name产品名称,还想要获得其他属性,新建t_category表的实体类:

Category.java

package mybatis.entity;

public class Category {
    private Integer categoryId;
    private String categoryName;
    private Integer parentId;
    private Integer categoryLevel;
    private Integer categoryOrder;

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Integer getCategoryLevel() {
        return categoryLevel;
    }

    public void setCategoryLevel(Integer categoryLevel) {
        this.categoryLevel = categoryLevel;
    }

    public Integer getCategoryOrder() {
        return categoryOrder;
    }

    public void setCategoryOrder(Integer categoryOrder) {
        this.categoryOrder = categoryOrder;
    }

}

修改DTO数据对象:

GoodsDTO.java

package mybatis.dto;

import mybatis.entity.Category;
import mybatis.entity.Goods;

/**
 * 扩展类,数据传输对象
 */
public class GoodsDTO {
    private Goods goods = new Goods();
    private Category category = new Category();
    private String test;

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}

修改映射结果集:

    <!--结果映射,通常id属性设置为rm+...,type指明要转化为的DTO -->
    <resultMap id="rmGoods" type="mybatis.dto.GoodsDTO">
        <!--设置主键字段与属性映射,必写-->
        <!-- property=goods.goodsId指的是,每次查到的goods_id字段的数据会为GoodsDTO类中goods属性对象的goodsId属性进行赋值 -->
        <id property="goods.goodsId" column="goods_id"></id>
        <!--设置非主键字段与属性映射-->
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"></result>
        <result property="category.categoryName" column="category_name"></result>
        <result property="category.parentId" column="parent_id"></result>
        <result property="category.categoryLevel" column="category_level"></result>
        <result property="category.categoryOrder" column="category_order"></result>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' as test from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

MyBatis数据写入

数据库事务

提到数据库的写入操作,就离不开数据库的事务。

数据库事务是保证数据操作完整性的基础

所有从客户端发来的新增修改删除操作,都会被事务日志所记录,我们形象的将事务日志看成流水账,它记录客户端发来的所有写操作的前后顺序,当客户端向MySQL服务器发起了一个commit提交命令的时候,事务日志才会将这三个数据同时的写入到数据表中,在commit的时候才是真正的往数据表写入的过程,当这三条数据都被成功写入到数据表中后,刚才所产生的事务日志都会被清空掉。
在这里插入图片描述
假设如果客户端在处理这些数据的时候,数据1和数据2执行成功,数据3因为各种原因没有执行成功的话,客户端会发起一个rollback回滚命令,当MySQL收到了rollback回滚命令后,当前事务日志中的所有已经产生的数据都会被清除,这就意味着前面已经产生的数据1和数据2不会放入到数据表中,只有当所有数据都完成的时候,在由客户端发起commit提交,数据才能成功的写入。
要么数据全部写入成功,要么中间出现了任何问题,全部回滚,保证了数据的完整性。

新增 - <insert>

    <insert id="insert" parameterType="mybatis.entity.Goods">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
        <!-- 该语句可以不写 -->
        <!-- 该语句表示插入goods对象后,获得插入后自动生成的主键值,并将该值保存(回填)到插入的goods对象的goodsId中 -->
       <!-- order-AFTER设置执行顺序,表示插入执行完毕后执行查询id回填;order-BEFORE代表之前执行 -->
       <!-- last_insert_id()是mysql内置函数用于获取当前连接最后产生的id号 -->
        <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
            select last_insert_id()
        </selectKey>
    </insert>

测试:

    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSqlSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = session.insert("goods.insert", goods);
            session.commit();//提交事务数据
            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

selectKey与useGeneratedKeys的区别

useGeneratedKeys方式:

    <insert id="insert" parameterType="mybatis.entity.Goods" useGeneratedKeys="true" keyProperty="goodsId" keyColumn="goods_id">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
    </insert>

二者区别 - 应用场景不同

  • selectKey适用于所有的关系型数据库,但编写麻烦,不同数据库迁移需要修改代码
  • userGenerateKeys只支持"自增主键"类型的数据库(DB2,Oracle等没有自增主键约束),但使用简单,会根据不同的数据库驱动自动编写查询语句,以下是该属性的使用方法

如果要在Oracle中获得新增后的主键,需要借助序列来实现,通过序列在执行新增语句之前生成一个新的序列值并保存到主键字段中。
在这里插入图片描述

更新 - <update> 与 删除 - <delete>

    <update id="update" parameterType="mybatis.entity.Goods">
        UPDATE t_goods
        SET
            title = #{title} ,
            sub_title = #{subTitle} ,
            original_cost = #{originalCost} ,
            current_price = #{currentPrice} ,
            discount = #{discount} ,
            is_free_delivery = #{isFreeDelivery} ,
            category_id = #{categoryId}
        WHERE
            goods_id = #{goodsId}
    </update>

    <delete id="delete" parameterType="Integer">
        delete from t_goods where goods_id = #{value}
    </delete>

测试:

    @Test
    public void testUpdate() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSqlSession();
            //利用之前写好的代码根据id获取数据,在此基础上进行更新调整0
            Goods goods = session.selectOne("goods.selectById", 739);
            goods.setTitle("更新测试商品");
            int num = session.update("goods.update" , goods);
            session.commit();//提交事务数据
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

    @Test
    public void testDelete() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSqlSession();
            int num = session.delete("goods.delete" , 739);
            session.commit();//提交事务数据
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSqlSession(session);
        }
    }

MyBatis预防SQL注入攻击

MyBatis两种传值方式

  • ${}文本替换,未经过任何处理对SQL文本替换
  • #{}预编译传值,使用预编译传值可以预防SQL注入

推荐使用#{}(将内容全部转为字符串)传值,之前写的程序都是使用的#{}传值,已经防止了SQL注入。

${}原文传值,需要从前台传递条件时可以使用,比如传入"order by title desc"。

MyBatis

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摘星喵Pro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值