整体介绍

◆讲解MyBaits基础入门 ◆讲解MyBatisi高级特性

MyBatis介绍

框架(Framework)的作用

生活中框架

软件开发中框架

◆框架是可被应用开发者定制的应用骨架 ◆框架是一种规则,保证开发者遵循相同的方式开发程序 ◆框架提倡”不要重复造轮子”,对基础功能进行封装 封装了增删查改等功能,解决JDBC操作麻烦的痛点,对立再统一形成框架

框架的优点

◆极大提高了开发效率 ◆统一的编码规则利于团队管理 ◆灵活配置的应用(XML/JSON等)拥有更好的维护性

SSM开发框架

Spring提供了底层对象管理,是框架下的框架,其他框架都要基于Spring进行开发,SpringMVC提供Web层面交互(替代Servlet),Mybatis提供对数据库的便捷操作(替代JDBC)

//可以看出技术从矛盾中产生,对立面通过第三方(用来解决对立)再统一成更高级的技术,形成复杂的现象,我们需要从矛盾中找出最主要的对立面即主要矛盾,次要矛盾由主要矛盾衍生而来

什么是Mybatis

◆MyBatis,是优秀的持久层框架 ◆MyBatis使用XML将SQL与程序解耦,便于维护 ◆MyBatis学习简单,执行高效,是JDBC的延伸 所谓持久就是将数据存入数据库以防重启丢失

mybatis – MyBatis 3 | 简介

MyBatis开发流程

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

单元测试与Unit4

单元测试

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

JUnit4使用方法

◆引入JUnit Jar包或增加Maven依赖 ◆编写测试用例验证目标方法是否正确运行 ◆在测试用例上增加@Test注解开始单元测试 下载并安装 ·junit-team/junit4 Wiki ·GitHub 两个核心文件

导入依赖的第一种办法:新建java工程,新建lib目录,导入两个jar包,在工程结构中导入两个依赖

导入依赖的第二种办法:新建maven工程,在pom.xml中写入

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13</version>
  <scope>test</scope>
</dependency>

导入成功后即可在外部库找到两个依赖包

推荐第二种方法导入依赖,maven工程有专门的test文件夹存放测试用例,可以用来测试类,一般命名方式为测试类名+Test/Testor.java

Calculater.java文件:

package com.imooc.junit;
 
public class Calculator {
    //加法运算
    public int add(int a , int b){
        return a + b;
    }
    //减法运算
    public int subtract(int a , int b){
        return a - b;
    }
    //乘法运算
    public int multiply(int a , int b){
        return a * b;
    }
    //除法运算
    public float divide(int a,int b){
        if(b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return (a*1f) / b;
    }
 
}

在test目录下新建CalculaterTest.java

import com.imooc.junit.Calculator;
import org.junit.Test;
 
public class CalculatorTest {
    private Calculator cal = new Calculator();
    //1.与原方法保持一致
    //2. 在原方法前增加test前缀
    @Test
    public void testAdd(){
        int result = cal.add(1, 2);
        System.out.println(result);
    }
 
    @Test
    public void testSubtract(){
        int result = cal.subtract(1, 2);
        System.out.println(result);
    }
 
    @Test
    public void testMultiply(){
        int result = cal.multiply(1, 2);
        System.out.println(result);
    }
 
    @Test
    public void testDivide(){
        float result = cal.divide(1, 2);
        System.out.println(result);
    }
    @Test
    public void testDivide1(){
        float result = cal.divide(1, 0);
        System.out.println(result);
    }
 
 
}

在Test.java文件运行即可得到答案结果,注意点击特定的函数名右键即可单独运行此函数,或者项目结构选中右键也可快速运行,右键项目名可以运行所有测试类

左边可看到所以函数方法运行成功 技巧:可以选中需要测试的java文件,选择codeGenerateTest,勾选需要指定的测试方法,即可按照原先目录结构快速创建测试类

MyBatis基本使用

MyBatis环境配置

使用xml保存配置信息,一般默认名为mybatis-config.xml文件 ◆MyBatis采用XML格式配置数据库环境信息 ◆MyBaits环境配置标签<environment> ◆environment包含数据库驱动、URL、用户名与密码

通过xml引入mybatis和mysql-connector-java两个驱动

<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.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!-- 如果同学用的是mysql8版本,可以改为:<version>8.0.19</version>   -->
            <version>5.1.47</version>
        </dependency>
    <dependencies>

右边栏选择DataBase,连接上数据库

Database选择数据库连接下的数据库名,在schemas下右键导入sql文件

也可以右键编辑表结构

配置好数据库后,接下来就是mybatis的环境配置 在resource目录下,新建xml文件,一般默认命名为mybatis-config.xml,主要配置在<configuration>标签对中

<?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>
 
    <!--设置默认指向的数据库-->
    <!--此处默认选择开发环境  若要使用生产环境,修改为prd即可-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <!--开发环境-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <!--<dataSource type="POOLED">-->
            <dataSource type="com.imooc.mybatis.datasource.C3P0DataSourceFactory">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <!--&符号不认识,需要转义为&amp;-->
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="user" value="root"/>
                <property name="password" value="20020829"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
                <property name="minPoolSize" value="5"/>
                <!--...-->
            </dataSource>
        </environment>
        <!--生产环境-->
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="20020829"/>
            </dataSource>
        </environment>
    </environments>
 
</configuration>

id属性表示命名环境名,从而可以便捷进行切换 需要引入mybatis – MyBatis 3 | 入门

<?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">

SqlSessionFactory

◆SqlSessionFactory是MyBatis的核心对象 ◆用于初始化MyBatis,创建SqlSession对象 ◆保证SqlSessionFactory在应用中全局唯一 sql的增删查改由这个工厂类创建出来的对象SqlSession进行调用

SqlSession

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

先加载mybatis-config.xml文件 在pom.xml加载junit

<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

然后再test文件夹下新建包com.imooc.mybatis 在包下新建MyBatisTestor.java测试类

package com.imooc.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 {
    /**
     * 初始化SqlSessionFactory
     * @throws IOException
     */
    //必须添加@Test注解,junit才会对其进行执行
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try {
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于与数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用 测试是否加载成功 此句代码mybatis会自动完成)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
}

初始化工具类MyBatisUtils

如何保证SqlSessionFactory全局唯一? java文件夹下新建com.imooc.mybatis.utis包 新建MyBatisUtils.java文件

package com.imooc.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;
 
/**
 * MyBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
 */
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }
 
    /**
     * openSession 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
 
    /**
     * 释放一个有效的SqlSession对象
     * @param session 准备释放SqlSession对象
     */
    public static void closeSession(SqlSession session){
        if(session != null){
            session.close();
        }
    }
}

使用static关键字修饰,属于类而不属于对象,可以直接通过类名调用 新增static块初始化静态对象

在MyBatisTestor.java测试类下即可进行测试

/**
     * MyBatisUtils使用指南
     * @throws Exception
     */
    @Test
    public void testMyBatisUtils() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

sqlsession内置了很多数据库的使用方法

MyBatis数据查询

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

如何提取商品数据表里面的数据?按照上面的步骤进行

  1. 新建包com.imooc.mybatis.entity 新增商品类,Goods.java,属性要与数据表中的字段对上 写好属性后,快速生成get与set方法

package com.imooc.mybatis.entity;
 
import java.util.List;
 
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;//分类编号
    private List<GoodsDetail> goodsDetails;
 
    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;
    }
 
    public List<GoodsDetail> getGoodsDetails() {
        return goodsDetails;
    }
 
    public void setGoodsDetails(List<GoodsDetail> goodsDetails) {
        this.goodsDetails = goodsDetails;
    }
}
  1. resourse目录下新建子目录mappers,表示映射器 保存的都是xml文件,新建goods.xml,用来说明实体类与哪个表和字段对应

映射器的dtd文件

<?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标签对 resultType表示数据返回的对象,sql语句执行后的每一条结果包装成实体对象,id表示sql语句名 namespace表示命名空间,类似java里面的包,用来进行区分,增加namespace,就能区分调用是哪个sql语句,相同namesapce下id属性唯一,但不同namespace下id属性可以相同

<?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="goods">
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
        select * from t_goods order by goods_id desc limit 10
    </select>
 
</mapper>

mybatis需要认识goods.xml文件,需要在mybatis-config.xml中进行声明

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

mybatis启动时会自动加载这个xml文件 3. 接下来在MyBatisTestor.java文件下进行测试

/**
     * select查询语句执行
     * @throws Exception
     */
    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectAll");
            for(Goods g : list){
                System.out.println(g.getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

右键 testSelectAll()单独运行

selectList是SqlSession的内置方法,根据我们需要查询所有的商品所以选择这个方法 getTitle()是我们定义的方法

数据库底层多单词使用了下划线分割(goods_id),但是在我们实体中属性强制使用驼峰(goodsId),导致属性与字段名不符,MyBatis无法帮我们完成设置工作,所以很多属性值为null

解决办法是在MyBatis-config.xml文件中,在<configuration>标签对里添加驼峰命名转换法:

<settings>
        <!-- goods_id ==> goodsId 驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

SQL传参

通过动态传入参数进行查询

单参数查询

在goods.xml中:

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

parameterType="Integer" 表示传入的参数为整形 使用#{value}接收参数 然后进行测试:

/**
     * 传递单个SQL参数
     * @throws Exception
     */
    @Test
    public void testSelectById() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById" , 1603);
            System.out.println(goods.getTitle());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

因为纸船一个参数,所以只有一条记录,选择方法selectOne()方法。1603由goods.xml中语句可知是goods_id,通过goods_id查出商品标题

多参数查询

goods.xml文件中添加

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

java.util.Map是采用键值对的方式保存数据,多参数打包成map就可以传入多参数数值了,resultType表示返回结果

继续进行测试:

/**
     * 传递多个SQL参数
     * @throws Exception
     */
    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("min",100);
            param.put("max" , 500);
            param.put("limt" , 10);
            List<Goods> list = session.selectList("goods.selectByPriceRange", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
 
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

如果保证goods.selectByPriceRange全局唯一,就可以不用加上goods了 因为多参数查询,有多条结果,所以使用selectList()方法

MyBatis获取多表关联查询结果

上面大的resultType获得的结果都是单表里面的,在工作中大部分是多表联合得到复杂的结果集 不仅参数可以使用map进行传入多个参数,结果集resultType也能使用map集将字段名与字段值封装成一个集合便于后续java操作

要求:对于t_goods这张表在查询数据时,将category这个字段也查询出来

<!-- 利用LinkedHashMap保存多表关联结果
        MyBatis会将每一条记录包装为LinkedHashMap对象
        key是字段名  value是字段对应的值 , 字段类型根据表结构进行自动判断
        优点: 易于扩展,易于使用
        缺点: 太过灵活,无法进行编译时检查
     -->
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
        select g.* , c.category_name,'1' as test from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

使用java.util.Map,里面的hashmap会由于自身机制(通过hash值)导致结果乱序,所以改用java.util.LinkedHashMap,LinkedHashMap是采用链表式存储,当提取数据时会按照插入顺序进行提取,所以不会乱序

比如第一张表有1,2字段,第二张表我们要第3字段,查询出来结果可能不会是1,2,3,而是2,3,1等结果顺序

使用LinkedHashMap非常灵活,比如添加上’1’ as test,就会增加test字段,值为1

集合一般使用Map来结尾 添加测试方法

/**
     * 利用Map接收关联查询结果
     * @throws Exception
     */
    @Test
    public void testSelectGoodsMap() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for(Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

ResultMap结果映射

通过LinkedHashMap也无法很好解决问题 如何用对象的方式保存多表关联查询的结果? ◆ResultMap可以将查询结果映射为复杂类型的Java对象 ◆ResultMap适用于Java对象保存多表关联结果 ◆ResultMap支持对象关联查询等高级特性

不再原来基础上进行重载,不要为了保存查询结果在原来类上修改,原来的类已经通过JDBC连接上数据库,修改耗费时间与精力,增加冗余属性,无法对应字段,不如重新新建一个拓展对象,对结果进行保存 新建包com.imooc.mybatis.dto 新建GoodsDTO.java dto是数据传输对象,dto一般都是为了在原有类上添加拓展

package com.imooc.mybatis.dto;
 
import com.imooc.mybatis.entity.Category;
import com.imooc.mybatis.entity.Goods;
//Data Transfer Object--数据传输对象
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 setCategory(String categoryName) {
        this.categoryName = categoryName;
 
    public String getTest() {
        return test;
    }
 
    public void setTest(String test) {
        this.test = test;
    }
}

private Goods goods = new Goods();创建了全新的Goods对象

添加属性Category和test继续对应字段 添加配置goods.xml

<select id="selectGoodsDTO" resultMap="rmGoods">
 
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
 
</select>

category_name和test字段在GoodsDTO里

使用resultMap,而不是resultType 我们需要新增resultMap标签,用来映射属性与字段名

<!--结果映射-->
    <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <!--设置主键字段与属性映射-->
        <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>

property表示属性名,column表示字段名一一映射下来 categoryName、test属于goodsDTO的,所以不用写前缀 t_goods数据表字段:

t_category数据表字段:

添加测试:

/**
     * 利用ResultMap进行结果映射
     * @throws Exception
     */
    @Test
    public void testSelectGoodsDTO() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list) {
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

其中List<GoodsDTO> list中GoodsDTO与type="com.imooc.mybatis.dto.GoodsDTO"中映射的GoodsDTO保持一致

MyBatis数据写入操作

前几小节学习的是MyBatis数据查询操作,这次重点放在MyBatis数据插入操作

数据库事务

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

提交成功后,事务日志清空

当数据中断,会发起回滚操作,不会写入到数据表中

MyBatis写操作包含三种

◆插入-<insert> ◆更新-<update> ◆删除-<delete>

插入-<insert>

<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">
        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})
      <!--<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">-->
          <!--select last_insert_id()-->
      <!--</selectKey>-->
    </insert>

进行测试

/**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            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.closeSession(session);
        }
    }

代码不写good_Id是因为我们插入数据时根本不知到是第几条了,所以一般设置为自动生成,即主键自增

然而我们发现新插入的数据,并没有将其Id回填到goodsId中,所以System.out.println(goods.getGoodsId());值为null,(id主键编号是会自动生成的)是不利于我们对数据的操作的 插入成功后如何获取到id主键? 增加配置项,使用selectKey标签对

<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id()
</selectKey>

order="AFTER"表示执行完插入操作后在执行select last_insert_id()语句 last_insert_id()是mysql自带的,表示当前连接最后产生的id号,resultType="Integer"为整形,承载这个最后id号

selectKey与useGeneratedKeys的区别

selectKey标签用法

useGeneratedKeys属性用法

二者区别-显示与隐示

◆selectKey标签需要明确编写获取最新主键的SQL语句 select last_insert_id() ◆useGeneratedKeys属性会自动根据驱动生成对应SQL语句 如果从mysql迁移到sqlserver,优先使用useGeneratedKeys,因为sql语句不兼容

二者区别-应用场景不同

◆selectKey:适用于所有的关系型数据库 ◆useGeneratedKeys只支特”自增主键”类型的数据库

总结

◆selectKey标签是通用方案,适用于所有数据库,但编写麻烦 ◆useGeneratedKeys属性只支持”自增主键”数据库,使用简单

更新-<update>

在goods.xml文件

<update id="update" parameterType="com.imooc.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>

通过goods_id进行选择更新 进行测试用例:

/**
     * 更新数据
     * @throws Exception
     */
    @Test
    public void testUpdate() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            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.closeSession(session);
        }
    }

不建议手动set组织数据,而是通过获取goods.selectById,再进行修改,这样对原数据影响最小

删除-<delete>

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

测试用例

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

MyBatis预防SQL注入攻击

SQL注入攻击

◆SQL注入是指攻击者利用SQL漏洞,绕过系统约束越权获取数据的攻击方式

MyBatis两种传值方式

◆${}文本替换,未经任何处理对SQL文本替换 ◆#{}预编译传值,使用预编译传值可以预防SQL注入 #{}预编译传值会将特殊符号一并传入,导致无法匹配,预防sql注入

<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
        ${order}
    </select>

测试用例

/**
     * 预防SQL注入
     * @throws Exception
     */
    @Test
    public void testSelectByTitle() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            /*
                ${}原文传值
                select * from t_goods
                where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'
            */
            /*
               #{}预编译
               select * from t_goods
                where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"
            */
 
            param.put("title","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order" , " order by title desc");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
****

为什么保留原文传值?比如我们需要排序时,无论升序还是降序,都需要动态传入,如果使用预编译,就会将 " order by title desc"看成字符串(保留双引号),而不是sql语句(不保留双引号)拼接上去

MyBatis工作流程