Mybatis

Mybaits官网:MyBatis中文网

文档地址:D:\MybatisTest

MybatisTest为父工程,MybatisTest1为子工程

大概的项目结构就这样,随便创建maven工程 

引入的依赖

<dependencies>
            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
            </dependency>

            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>

            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
            </dependency>

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

......先创建个数据库名 mybatis

utf8和utfmb4区别:

utfmb4 兼容 utf8 且可以显示更多的字符,一般都用不到,选择utfmb4,只会浪费空间,通常,用的utf8,我喜欢用utf8mb4(个人习惯);

排序

常用的就是 utf8_general_ci

utf8_general_ci:不缺分大小写,核对速度快,准确度稍差

utf8_general_cs:区分大小写

utf8_bin:字符串每个字符串用二进制数据编译存储。 区分大小写,而且可以存二进制的内容

utf8_unicode_ci:准确度高,但校对速度稍慢
 

然后表


CREATE TABLE `user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
  `user_name` varchar(50) NOT NULL comment '用户名',
  `age` smallint(6) NOT NULL comment '年龄',
  `addr` varchar(100) default NULL comment '地址',
  `gender` varchar(10) default NULL comment '性别',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '用户表';
insert into user(user_name,age,addr,gender) values ('哈哈',18,null,null);
insert into user(user_name,age,addr,gender) values ('张三',33,'北京朝阳区',null);
insert into user(user_name,age,addr,gender) values ('李四',66,'陕西西安',null);


有表就肯定有对应的实体类

运用Mybatis的第一种方式:

所有的都是官网文档抄的,但是学会抄,学会看文档没有3-5年经验还是比较麻烦的,当然我属于笨的.

大概的逻辑,其实mybatis-config.xml这个名字随你自己

 以下是各个文件的所有配置信息,且注释都写的较为详细

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>
    <!--
       定义一些键值对,可以在当前文件中通过${}来使用,也可以在当前项目任意的mapper.xml文件中使用(就是下面指定的mapper文件)
       resource:指定外部文件
   -->
    <properties resource="db.properties">
        <property name="aaaa" value="bbbb"/>
    </properties>
    <!--
        environments:数据库环境配置
        default:默认使用哪个库
        id:指定使用的数据库id
    -->
    <environments default="development">
        <environment id="development">
            <!--
                transactionManager:事务管理器
                type取值有两种:
                1:type="JDBC",使用java.sql.Connection管理事务
                2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:数据源
                type="POOLED",使用连接池
                type="UNPOOLED",不使用连接池
                type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--告诉mybatis去哪里找mapper文件-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

UserMapper.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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
    <select id="selectUserById" resultType="com.hrui.pojo.User">
        select * from user where user_id = #{userId}
    </select>
</mapper>

db.properties

测试的结果

 与数据库并不一致,原因,实体类字段是驼峰命名

 下面介绍下目前情况下,解决此问题的几种方式:

1.最简单的,通过sql

<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
    <select id="selectUserById" resultType="com.hrui.pojo.User">


        select
            user_id as userId,
            user_name as userName,
            age,
            gender
            from user where user_id=#{userId}
    </select>
</mapper>

这样就解决了.

2.通过resultMap

<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
    <resultMap id="userMap" type="com.hrui.pojo.User">
        <!--只需要配置命名不同的,相同的配置了也可以-->
        <result property="userId" column="user_id"></result>
        <result property="userName" column="user_name"></result>
    </resultMap>
    <select id="selectUserById" resultMap="userMap">/*这里需要改成resultMap*/

        select * from user where user_id=#{userId}
    </select>
</mapper>

也可以解决

3.在mybatis-config里配置,注意下此时UserMapper.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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
    <select id="selectUserById" resultType="com.hrui.pojo.User">
        select * from user where user_id = #{userId}
    </select>
</mapper>

在mybatis-config.xml里配置下面这个

在mybatis-config.xml配置的时候注意一个顺序,就是configuration里按下面排序来写,别问为什么,规定.......

 看下输出,日志也可以看到了

还可以在mybatis-config.xml中指定实体包别名,这样以后resultType="user"就不用写全名,只要类名小写就可以了

这里提前讲一点,关于MyBatis的事务与缓存

MyBatis框架是对JDBC的封装.

看下图

 MyBatis框架事务控制方式有两种

1.type="JDBC":让java.sql.Connection对象管理事务(下面说这种)

2.type="MANAGED":让外在容器管理事务

在type="JDBC"情况下:

Connection 对象的 setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交。

Connection通过commit()方法提交事务,国通rollback()方法回滚事务.但是默认情况下,Mybatis将自动提交功能关闭了,改为了手动提交.即程序中需要显示的对事务进行提交或回滚.看下原先输出的日志

在获得SqlSession的时候,如果openSession(),是无参或者是false,则必须手工提交事务,

如果openSession(true),则为自动提交事务,在执行完增删改后无需commit(),事务自动提交,我们可以发现,在使用SpringMVC或者是SpringBoot整合Mybatis时,我们执行增删改后无需commit(),

那么可以肯定设置了openSession(true)

---------------------------------------------------------------------------------------

缓存:将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能。

缓存执行机制:在进行数据库访问时,首先去访问缓存,如果缓存中有要访问的数据,则直接返回客户端,如果没有则去访问数据库,在库中得到数据后,先在缓存放一份,再返回客户端。如下图。

mybaits提供一级缓存和二级缓存。默认开启一级缓存。

关于一级缓存和二级缓存暂时可以这么理解:sqlSession层面的是一级缓存

Mapper(namespace)层面属于二级缓存

看下具体代码执行 

 如图只进行了一次查询

 那么我代码改下:如图 如果close了  那么其实就是另外一个sqlSession,缓存自然清空了

执行结果

再看下,同一个sqlSession如果commit了,那么也会清空缓存

 那么结论就是:同一个sqlSession会保留一级缓存,因为SpringMVC,SpringBoot整合Mybatis时默认增删改是自动commit的,因此数据更新后,同一个sqlSession内的缓存会消失,再次查找时自动缓存.

Mybatis默认在sqlSession层面开启了一级缓存,如何开启二级缓存:

mybaits的二级缓存是mapper范围级别,除了在mybatis-config.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存,并且要让实体类实现serializable接口。

具体步骤:

1.在mybatis配置文件中加入下面语句

 2.在UserMapper.xml文件中开启二级缓存,使用<cache></cache>

3.实体类实现序列化接口java.io.serializable

 

 二级缓存结论是:只要有一个sqlsession.commit,后多个sqlSession会公用一个Mapper

这里本想先说下resultMap,一会吧

因为现在还没有用到springBoot,以后在SpringBoot中配置相关如下

在SpringBoot中yml文件配置mybatis和mybatis-plus

总结mybatis的第一种写法:

1.创建mybatis的配置文件mybatis-config.xml,名字随便取

2.创建SqlSessionFactory

3.通过SqlSessionFactory创建SqlSession

4.创建Mapper文件

5.使用SqlSession对象执行sql

下面介绍mybatis的最终写法(mapper接口写法)

1.创建接口,写对应抽象方法,如图

2.改写UserMapper.xml,改写命名空间namespace为接口全名,方法名为id

3.执行

 

很多人在学习Mybatis时,可能遇到过很多版本,老师有时会讲将接口和.xml放同一包中,且名字要相同,导致学习很混乱,怎么一会必须同包中且名字要相同,一会又可以不用放在一起,名字也可以不同

这里带你完全解开这个结(因为自己是强迫症患者,曾经在不太明白是这个问题困扰很久,现在也还是小白)

1.在注册映射文件时使用<mappers><package name="包名"></mappers>标签时,需要映射文件名和接口名一样,不然会报错。

2.在注册映射文件时使用<mappers><mapper class="">mapper</mappers>标签的class属性时,需要映射文件名和接口名一样,不然会报错。

3.在注册映射文件时使用<mappers><mapper resource="org/xx/demo/mapper/xx.xml"/></mappers>,不需要映射文件名和接口名一样
 

另外相信很多同学在学习Mybatis时很肯定多个参数时候必须加@Param注解

那么也同样的告诉你在mybatis 3.4.1之后StringBoot整合,允许不加 @Param指定参数名称,自动会以入参的名称作为param key,嘿嘿.

第二种方式总结(最终写法)

1.创建mybatis的配置文件mybatis-config.xml,名字随意

2.创建sqlSessionFactory

3.通过sqlSessionFactory创建sqlSession

4.创建mapper接口,在.xml里指定namespace为接口全名 sql id为接口方法名

5.使用sqlSession对象获取接口代理

6.使用代理对象执行

简单增删改:

新增:

执行

这里注意 try()实现了Closeable可以自动关闭,但是并没有提交,因此这样执行是没有效果的

 

如果我没有设置自动提交,而主键自增策略已开启,这个自己领悟

这里另外讲下:关于mybatis-config.xml的多环境配置

官方文档:

 修改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>
    <!--
       定义一些键值对,可以在当前文件中通过${}来使用,也可以在当前项目任意的mapper.xml文件中使用(就是下面指定的mapper文件)
       resource:指定外部文件
   -->
    <properties resource="db.properties">
        <property name="aaaa" value="bbbb"/>
    </properties>
    <settings>
        <!--开启日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别 -->
        <setting name="mapUnderscoreToCamelCase" value="true" />
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <!--指定实体类包别名-->
    <typeAliases>
        <package name="com.hrui.pojo"/>
    </typeAliases>
    <!--
        environments:数据库环境配置
        default:默认使用哪个库
        id:指定使用的数据库id
    -->
    <environments default="development">
        <environment id="development">
            <!--
                transactionManager:事务管理器
                type取值有两种:
                1:type="JDBC",使用java.sql.Connection管理事务
                2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:数据源
                type="POOLED",使用连接池
                type="UNPOOLED",不使用连接池
                type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>

        <!--另外配置一个数据源development2-->
        <environment id="development2">
            <!--
                transactionManager:事务管理器
                type取值有两种:
                1:type="JDBC",使用java.sql.Connection管理事务
                2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:数据源
                type="POOLED",使用连接池
                type="UNPOOLED",不使用连接池
                type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver1}"/>
                <property name="url" value="${url1}"/>
                <property name="username" value="${username1}"/>
                <property name="password" value="${password1}"/>
            </dataSource>
        </environment>
    </environments>

    <!--告诉mybatis去哪里找mapper文件-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

在db.properties里,多加个配置

 

 如上简单的多数据源配置

Mybatis通过SqlSessionFactory获得SqlSession然后去执行sql

关于SqlSessionFactory可以查看简述,觉得写的很好SqlSessionFactory创建过程 - 简书

重归正题:

简单删除

 

简单更新:

两种方式返回自动生成的主键:实际应用的业务场景很多,比如往一张表插入数据后,需要再另外一张表里对刚插入的数据的Id做记录

第一种方式:useGeneratedKeys+keyProperty

以原先插入数据为例子

 

这里我再做个例子,一般情况,建表时候会通过mysql自增来生成主键,打个比方,我现在希望的主键

为NBXX00000001这种方式存在,且不讨论这样降低查询效率的事.如何实现呢

建张test表测试下

CREATE TABLE `test` (
  `fhbs` varchar(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `age` smallint NOT NULL COMMENT '年龄',
  `addr` varchar(100) DEFAULT NULL COMMENT '地址',
  `gender` varchar(10) DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`fhbs`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3 COMMENT='测试表';

现在主键为字符串类型,因此将自增去掉

建对应的实体类

在mybatis-config.xml里将.xml配置上去   

 创建接口 

 

 

下面这个工具类用以返回NBXX0000000001格式的

public class PrimaryKey {
    // 数字串最小值
    private static final char MIN_DATA = '0';
    // 数字串最大值
    private static final char MAX_DATA = '9';
    // 数字串默认从1开始
    private static final char START_DATA = '1';
    // 默认长度
    private static final int DEFAULT_SIZE = 8;
    // KeySize的最大数
    // Long的最大长度是19位,防止溢出
    private static final int MAX_KEYSIZE_VALUE = 18;
    // 默认字符串Head
    private static final String DEFAULT_HEAD = "KEY";
    // 主键字符串头部
    private String keyHead;
    // 字符串数字位数,不足补0
    private Integer keySize = 8;
    // 是否允许数字最大之后自增,默认false
    private boolean keyInc = false;
    // 程序执行开始系统时间
    private Long startExecute = 0L;
    // 程序执行结束系统时间
    private Long finishExecute = 0L;

    /**
     * 初始化主键字符串格式,默认达到KeySize后不可自增
     * @param keyHead 字符串开头部分
     * @param keySize 字符串数组长度
     */
    public PrimaryKey(String keyHead, Integer keySize) {
        super();
        /**
         * 设置不可自增
         */
        if(this.checkSize(keySize))
            this.keySize = keySize;
        else
            this.keySize = this.DEFAULT_SIZE;
        if(this.checkHead(keyHead))
            this.keyHead = keyHead;
        else
            this.keyHead = this.DEFAULT_HEAD;
    }

    /**
     * 初始化主键字符串的格式
     * @param keyHead 字符串开头部分
     * @param keySize 字符串数组长度
     * @param keyInc 数值最大值之后是否允许自增
     */
    public PrimaryKey(String keyHead, Integer keySize, boolean keyInc) {
        super();
        if(this.checkSize(keySize))
            this.keySize = keySize;
        else
            this.keySize = this.DEFAULT_SIZE;
        if(this.checkHead(keyHead))
            this.keyHead = keyHead;
        else
            this.keyHead = this.DEFAULT_HEAD;
        this.keyInc = keyInc;
    }

    /**
     * 返回下一个字符串
     * @param currentKey 当前主键
     * @return 正常:下一个主键值 = 当前主键 + 1;
     * 		   当字符串数字达到KeySize的最大数时
     * 		   KeyInc为true时, 下一个主键字符串返回最大数 + 1
     * 		   KeyInc为false时, 返回空值
     */
    public synchronized String nextKey(String currentKey) {

        // 记录开始执行程序系统时间
        this.startExecute = System.currentTimeMillis();
        try {
            /**
             * 去掉首尾空字符
             */
            currentKey = currentKey.trim();
            if(!this.check(currentKey)) {
                System.out.println(PrimaryKey.class.getSimpleName() +
                        " Error: Input CurrentKey Str Type Illegal, Check '" + currentKey +"' is Right!");
                return null;
            }
            StringBuilder sb = new StringBuilder();
            sb.append(this.keyHead);
            int charIndex = 0;
            for(int i = 0; i < currentKey.length(); i++) {
                char symbol = currentKey.charAt(i);
                if(symbol >= this.MIN_DATA && symbol <= this.MAX_DATA) {
                    charIndex = i;
                    break;
                }
            }
            String dataStr = currentKey.substring(charIndex, currentKey.length());
            Long dataNum = Long.valueOf(dataStr);
            dataNum++;
            if(dataNum < this.splitDataPosition()) {
                for(int i = 0; i <= this.keySize - String.valueOf(dataNum).length() - 1; i++) {
                    sb.append(this.MIN_DATA);
                }
                sb.append(dataNum);
            }else if(dataNum >= this.splitDataPosition() &&
                    dataNum < this.maxDateNumber()) {
                sb.append(dataNum);
            }else{
                // 超过大小最大数时
                if(this.keyInc) {
                    sb.append(dataNum);
                }else{
                    // 允许自增标志位false的时候返回空值
                    return null;
                }
            }
            return sb.toString();
        } catch (Exception e) {
            System.out.println(e.toString());
            return null;
        } finally {
            this.finishExecute = System.currentTimeMillis();
//			System.out.println(PrimaryKey.class.getSimpleName() + " nextKey() Execute: "
//					+ (this.finishExecute - this.startExecute) +"ms.");
        }
    }

    /**
     * 获取初始化字符串
     * @return
     */
    public synchronized String initStartKey() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.keyHead);
        for(int i = 0; i < this.keySize - 1; i++) {
            sb.append(this.MIN_DATA);
        }
        sb.append(this.START_DATA);
        return sb.toString();
    }

    /**
     * 获取需要补零的最大数字
     * @return
     */
    private Long splitDataPosition() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.START_DATA);
        for(int i = 0; i < this.keySize - 1; i++) {
            sb.append(this.MIN_DATA);
        }
        return Long.valueOf(sb.toString());
    }

    /**
     * 获取最大数
     * @return
     */
    private Long maxDateNumber() {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < this.keySize; i++) {
            sb.append(this.MAX_DATA);
        }
        return Long.valueOf(sb.toString());
    }

    /**
     * 简单的验证空值
     * @param key
     * @return
     * @throws Exception
     */
    private boolean check(String key) throws Exception {
        try {
            // 空值验证
            if(key == null || key.equals(""))
                return false;
            // key字符串长度验证
            if(key.length() <= this.keyHead.length())
                return false;
            // 是否符合初始化串开头验证
            String head = key.substring(0, this.keyHead.length());
            if(!head.equals(this.keyHead))
                return false;
            /**
             * 串数字长度验证,当允许最大熟自增时候不检测
             * 当不允许达到最大数字时验证长度合法性
             */
            String data = key.substring(this.keyHead.length(), key.length());
            if(data.length() != this.keySize && !this.keyInc)
                return false;
            // 验证是否是数字串,通过一个转换变量
            for(int i = 0; i < data.length(); i++) {
                char symbol = data.charAt(i);
                if(symbol > this.MAX_DATA || symbol < this.MIN_DATA) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 验证输入的KeySize合法性
     * @param keySize
     * @return
     */
    private synchronized boolean checkSize(Integer keySize) {
        if(keySize != null && keySize > 0
                && keySize <= this.MAX_KEYSIZE_VALUE)
            return true;
        return false;
    }

    /**
     * 验证输入的KeyHead,条件全部要求是字母
     * @param keyHead
     * @return
     */
    private synchronized boolean checkHead(String keyHead) {
        if(keyHead != null && !keyHead.equals("")) {
            for(int i = 0; i < keyHead.length(); i++) {
                char symbol = keyHead.charAt(i);
                if(symbol >= this.MIN_DATA && symbol <= this.MAX_DATA) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

}

因为没有自增,因此第一次插入时候需要直接给值,以后插入就可以根据返回的主键来

 

 感觉效果并不好,当然一般进行插入时候前端返回回来比如说NBXX000000005,放进去可以生成一个NBXX000000006出来,但感觉太low

这里一会补上....

第二种方式:selectKey子元素

测试

 

关于下划线列明自动转驼峰和sql日志打印,一开始我们就配好了,还有很多其他配置

可以查看官方文档

 

 关于日志打印,

 下图写错了.........

 如果是如下配置,需要引入具体jar包,比如下面需要引入SLF4J,因为我本身引入了lombok就自然有了SLF4J的jar包,下图,原先图配错了,后期补了下

注意下,再下面演示

returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)

时候,当版本改成3.4.2以上后  log日志如果单纯依赖lombok就出错了 所以还是引入下依赖

 关于日志文件

 

关于列值为空的处理

当该列为null时,说明mybatis是不会处理这些列的 

测试

 

如何将为null的数据也返回回来呢?配置下

 

 关于ReturnInstanceForEmptyRow,如图举例

 这种情况,如果我们 select addr form user where user_id=1去查询

 

解决办法

 结果...........................

问题出在了哪呢?

 

 关于入参的讲究

当入参只有只有一个参数且是基本类型(含包装类)或者字符串

mapper接口中的参数名字随意取

比如

 返回为null其实就是可以

 再测一个

当参数为conllection/list/set/数组的时候 

测试参数为Collection

比如我们要查询 user_id为 1,2,3的用户集合

select * from user where user_id in(1,2,3);

 

现在下面index去掉没事

 再换种写法,一样可以

 

 再换种写法,将collection="collection"

 也是可以的

所以当参数为Collection   或者List的时候  collection="list" conllection="conllection"都可以

当是Set时候要用collction     是数组时候  要用array不演示了

当参数是javabean时候,就是比如User user的时候,写该实体类的属性名即可(前面写过很多,不演示)

当参数为Map的时候

测试参数为多个的时候 

比如现在我想查询user表中年龄小于30且名字含有"小"字的用户

 

user改成user_name

 那将参数换成arg0,arg1试试

 

 测试成功 说明用arg0,arg1是可以的  如果还有就用arg2  arg3  arg4 .....

用param1  param2试试

 

 

 测试结果:用param1 param2也是可以的   如果还有  param3 .....

那么一定要用arg0 arg1或者param1  param2吗   如何用有意义的参数名或者说如何自定义参数名

第一种方法:在userActualParamName的值是true的情况下加入编译参数也就是说这里userActualParamName默认情况下true,加入的是编译参数,如何改成自定义参数名,改成false行不行呢??

在官方文档

 在mybatis-config.xml里配置下

意思就是说

useActualParamName=true的时候可以用 arg0 arg 1或者param1 param2

useActualParamName=false的时候可以用 0  1或者param1 param2

下面做个小demo

通过反射来获取a1方法里的参数名

引入spring框架

惊不惊喜,意不意外(这就是MyBatis3.4.1之后在Spring整合MyBatis和Spring整合MyBatis不需要@Param的原因吗,没人和我说我猜的)

这里是因为我引入了Spring框架

不适用Spring框架怎么办呢?

在编译时候加上

<configuration>

        <parameters>true</parameters>

</configuration>

效果和使用Spring的工具类LocalVariableTableParameterNameDiscoverer的效果一样都是获取真正的方法参数名

具体使用在子工程的下面加上,就是说在编译时候把参数名变成自定义的

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <parameters>true</parameters>
                </configuration>
            </plugin>
        </plugins>
    </build>

重新编译下,另外重新将这个改回来,默认就是true,还是让它使用编译后的参数

只不过现在我们编译后的参数名是自定义的了

 

 

 哇哦哇哦

 SpringBoot就是这么干的

第二种方法:通过@Param注解指定(SpringBoot整合Mybatis3.4.1版本后不用加@Param注解)

这个就不演示了....

关于MyBatis封装入参的原理:org.apache.ibatis.reflection.ParamNameResolver#getNamedParams

MyBatis   ps.setXxx取值原理:

org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

关于返回值的问题insert就是插入几条返回几,delete也一样

update的话需要注意下

新写个demo

 

 这里有个问题,就是无论更新了多少次,这个返回值一直是1

我们无法看出来,到底数据更新了没有,还是说和原先比一个值都没变,这个问题如何解决,这里用Mybatis就没有用了,要依靠JDBC来解决

具体做法

在db.properties的url后面加上&useAffectedRows=true,这样就返回真正影响的行数

我们再次更新一条和数据库一样的数据时候,返回值为0

关于select的返回值

常用的不写了,写点不常用的

比如说返回List<user>时候,

 

这个resultType是里面javabean

比如返回List<Map<String,Object>> 

 

 关注下这种数据格式

[{gender=null, user_id=1, user_name=小红, addr=null, age=18}, {gender=null, user_id=2, user_name=张三, addr=北京朝阳区, age=33}, {gender=null, user_id=3, user_name=李四, addr=陕西西安, age=66}]

关于resultMap这里要讲下,它其实上面已经说过了,可以帮助我们减少后端代码,

比如说关联表的查询,1对1,1对多等等

在1对1关系中使用association

1对多关系中使用conllection

举例说明

先插入三张表

drop table if exists author;
CREATE TABLE `author` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
  `name` varchar(50) NOT NULL comment '作者名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '作者表';
insert into author(id,name) values (1,'哈哈');
insert into author(id,name) values (2,'呵呵');

drop table if exists article;
CREATE TABLE `article` (
  `article_id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
  `title` varchar(50) NOT NULL comment '标题',
  `author_id` bigint(20) not NULL comment '外键,作者表的id',
  PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '文章表';
insert into article(article_id,title,author_id) values (1,'mybatis使用教程',1);
insert into article(article_id,title,author_id) values (2,'mybatis源码剖析',2);

drop table if exists remark;
CREATE TABLE `remark` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
  `cont` varchar(500) NOT NULL comment '评论内容',
  `article_id` bigint(20) not NULL comment '所属文章id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '评论表';
insert into remark(id,cont,article_id) values (1,'这个教程是良心教程,学到了很多',1);
insert into remark(id,cont,article_id) values (2,'666+999',1);
insert into remark(id,cont,article_id) values (3,'分析的不错',2);

作者表,文章表,评论表.

站在文章表的立场上:文章表里每条数据都对应作者表的每条数据(作者).1对1关系

站在文章表的立场上:文章表里的每条数据都对应着评论表里的多条数据.1对多关系

刚好可以做演示

建完表,第一件事一般就是建实体类

 

就不建VO了,直接在实体类上加了

比如先查出article表的所有信息  用resutlMap的使用

 

 

 

 通过resultMap里的association用于1对1关系

 

resultMap使用collection处理1对多

 

foreach实现批量插入

一般批量插入

insert into user(user_name,age) values('a',1),('b',2),('c',3)

 

以上只是插入几十条,几百条数据,那么如果我需要插入10W条数据呢

居然没报错,10W条数据花了20秒

那么100W条数据呢,几分钟还没完,下去买吃的

100W条耗时622秒

本来是想演示报错的 如下图

 结果这样插入居然全成功了,可能是我用的阿里云RDS数据库的原因?????????

咱就当它报错了行吧

 这种批量插入报错,因为mysql对要执行的sql语句长度有限制,所有有可能会失败

 

 晕,一般mysql对sql语句的默认长度好像是,查了下本机 4M

我这查了阿里云的是配置了1G.....

查询语句:show VARIABLES WHERE Variable_name LIKE 'max_allowed_packet'

或者show VARIABLES like '%max_allowed_packet%'

 此种方式的插入对于少量数据影响不大

但是大量数据时候,一方面是mysql的sql语句长度限制,当然长度限制应该是可以修改的,最重要的一点,太耗时,刚才我100W的数据量插入耗时10分钟

下面介绍一种MyBatis用于批量插入的另一种方式   MyBatis批量插入执行器,BatchExecutor批量插入

表面上看是由SqlSession去执行sql,其实真正执行sql的是Executor

 

这样更慢 耗时 

不是批量的话,执行流程:预编译->设置参数->执行.如此往复

批量的话:程序把sql发到mysql,让mysql做预编译,然后一次性提交

但是这里不能这么做,一般建议2000到1W条之间提交一次 

 这样也不行,这里暂停吧.改天专门写篇批量处理的

关于sql和include标签

 

 

 ​​​​​​​

 

 关于if和where

 

if的作用

 测试

 怎么办呢?

测试 ,注意下刚开始报错了,后来将注释删除了就好了,说明/* xxxxxx */注释影响sql执行

 

 <where>标签,比如 userName没有值  age有值

那么sql会变成where and age>=2   这样<where>标签会自动将这个and或者or去掉

演示下

关于choose和when和otherwise

为了演示,RequestPage里新加两个字段

比如现在有这样一个逻辑

如果排序的字段不是null或者"",就按你传过来的这个字段排序,不然就按XXX排序

现在还没有传值

sortField写错了下面

这里注意下

 关于set标签:用于动态包含需要更新的列,忽略其他不更新的列

比如原先写过的一个更新例子

 

 

 比如我现在只想更新名字,其他的不更新看下数据库

结果原先的地址没有了

 改下

结果执行

 那么这个逗号怎么去掉呢,其实和where的问题差不多

用<set>会去掉末尾的逗号

 

关于trim标签

trim标签包含了where和set的功能

以上面的set为例

 用于避免相同数据的更新返回值为1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hrui0706

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

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

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

打赏作者

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

抵扣说明:

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

余额充值