Mybatis

作者: 请叫我平爷 | 来源:发表于2022-03-03 19:50 被阅读0次

    Mybatis

    常见

    #{} 和 ${}

    • {}:sql占位符

    • ${}:字符替换,properties中的变量占位符

    Mybatis使用方式

    • 方式一:

    Mapper类

    public interface StudentMapper {
        List<Student> selectAll();
    }
    

    StudentMapper.xml

    <select id="selectAll" resultMap="BaseResultMap">
        select `name`, code
        from student
    </select>
    
    • 方式二:

    Mapper类:

    public interface StudentMapper{
        @Select("select * from student")
        List<Student> selectAll();
    }
    
    • 方式三

    Mapper类:

    public interface StudentMapper{
        @SelectProvider(type= StudentProvider.class,method="selectStudentById")
        Student selectStudentById(@Param(value="code") String code);
    }
    

    StudentProvider

    public class StudentProvider {
        public String selectStudentById(Map<String, Object> para){
            return new SQL(){{
                SELECT("*");
                FROM("student");
                WHERE("code="+para.get("code"));
            }}.toString();
        }
    }
    

    获取自增长主键id

    Mysql:

    • 方式一:
    <insert id="insert" parameterType="com.mi.learn.mybatis.domin.Afinfo">
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
          SELECT LAST_INSERT_ID()
        </selectKey>
        insert into afinfo (`name`, age, birth, 
          sex, memo)
        values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE}, 
          #{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
    </insert>
    
    • 方式二
    <insert id="insertAfinfo" parameterType="com.mi.learn.mybatis.domin.Afinfo" useGeneratedKeys="true" keyProperty="id">
        insert into afinfo (`name`, age, birth,
          sex, memo)
        values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE},
          #{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
    </insert>
    

    Oracle:

    // 这个是创建表的自增序列
    CREATE SEQUENCE student_sequence
    INCREMENT BY 1
    NOMAXVALUE
    NOCYCLE
    CACHE 10;
    
    // 方式一,使用 `<selectKey />` 标签 + BEFORE
    <insert id="add" parameterType="Student">
      <selectKey keyProperty="student_id" resultType="int" order="BEFORE">
          select student_sequence.nextval FROM dual
        </selectKey>
        
         INSERT INTO student(student_id, student_name, student_age)
         VALUES (#{student_id},#{student_name},#{student_age})
    </insert>
    
    // 方式二,使用 `<selectKey />` 标签 + AFTER
    <insert id="save" parameterType="com.threeti.to.ZoneTO" >
        <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" >
          SELECT SEQ_ZONE.CURRVAL AS id FROM dual
        </selectKey>
        
        INSERT INTO TBL_ZONE (ID, NAME ) 
        VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR})
    </insert>
    

    Mapper传递多个参数

    • 使用Map传递
    Afinfo selectAfinfoWithMap(Map<String,Object> map);
    
    <select id="selectAfinfoWithMap" parameterType="Map" resultType="com.mi.learn.mybatis.domin.Afinfo">
        select id, `name`, age, birth, sex, memo
        from afinfo
        where `name` = #{name} and age=#{age}
      </select>
    
    • 使用@Param传递
    Afinfo selectAfinfoWithParam(@Param("name") String name,
                                  @Param("age") Integer age
                                  );
    <select id="selectAfinfoWithParam" resultType="com.mi.learn.mybatis.domin.Afinfo">
        select id, `name`, age, birth, sex, memo
        from afinfo
        where `name` = #{name} and age=#{age}
      </select>
    
    • 不使用@Param
    Afinfo selectAfinfoWithObject(String name,
                                   Integer age);
    
    <select id="selectAfinfoWithObject" resultType="com.mi.learn.mybatis.domin.Afinfo">
        select id, `name`, age, birth, sex, memo
          from afinfo
          where `name` = #{param1} and age=#{param2}
      </select>                               
    

    批量插入

    • for循环调用单条插入
    for (int i=0;i<5; i++){
        mapper.insertAfinfo(afinfo);   
    }
    
    <insert id="insertAfinfo" parameterType="com.mi.learn.mybatis.domin.Afinfo" useGeneratedKeys="true" keyProperty="id">
        insert into afinfo (`name`, age, birth,
          sex, memo)
        values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE},
          #{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
    </insert>
    
    • 批量插入
    mapper.insertBatch(list);
    
    <insert id="insertBatch" parameterType="list" useGeneratedKeys="true" keyProperty="id">
        insert into afinfo (`name`, age, birth,
        sex, memo) values
        <foreach collection="list" item="item" index="index" separator=",">
        (#{item.name,jdbcType=VARCHAR}, #{item.age,jdbcType=INTEGER}, #{item.birth,jdbcType=DATE},
          #{item.sex,jdbcType=CHAR}, #{item.memo,jdbcType=VARCHAR})
        </foreach>
    </insert>
    

    控制台打印sql日志

    logging.level.com.mi.learn.mybatis.mapper=debug
    

    Mybatis缓存

    # 开启缓存
    mybatis.configuration.cache-enabled=true
    

    Mybatis和Hibernate

    • Hibernate 全自动ORM映射工具,可以查询关联对象或者关联集合对象,可以根据对象关系模型直接获取
    • Mybatis属于半自动ORM映射工具,在查询关联对象或关联集合对象时,需要手动编写SQL

    转义字符

    转义字符 字符 含义
    < < 小于
    > > 大于
    & &
    ' ' 单引号
    " " 双引号

    或者

    <![CDATA[    ]]>
    

    XML标签

    <select></select>
    <insert></insert>
    <update></update>
    <delete></delete>
    <resultMap></resultMap>
    <parameterMap></parameterMap>
    <sql></sql>
    <include></include>
    <selectKey></selectKey>
    
    

    sql、include标签

    List<Afinfo> selectAfinfoWithObject(String name,
                                   Integer age);
    
    <sql id="student_element">
        id,`name`,age,birth,sex,memo
    </sql>
    
    <select id="selectAfinfoWithObject" resultType="com.mi.learn.mybatis.domin.Afinfo">
        select <include refid="student_element"></include>
          from afinfo
          where `name` = #{param1} and age=#{param2}
      </select>
    

    xml和Mapper接口的工作原理,Mapper接口参数不通,能否重载

    • xml文件与Mapper类通过namespace来一一对应
    • Mapper接口是没有实现类的,通过调用方法,mapper类的包名+接口名为key
      • 如:com.mi.learn.mybatis.mapper.AfinfoMapper.insert
        • 可以找到namespace为com.mi.learn.mybatis.mapper.AfinfoMapper下
        • id为insert的MappedStatement(映射语句)
        • 每个 select、update、delete、insert都会被解析成一个MapperStatement(映射语句)
    • Mapper接口是不能重载的,因为是通过包名+方法名去保存和寻找
    • Mapper接口的工作原理是JDK动态代理
      • Mybatis运行时会通过JDK动态代理把Mapper接口生成代理proxy对象
      • 代理对象proxy会拦截接口方法
      • 改成执行MapperStatement所对应的sql
      • 将sql执行的结果返回

    Mybits分页

    • RowBounds对象进行分页,对ResultSet结果集执行内存分页
    • 分页插件原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法中拦截待执行的sql,重写sql,添加对应的物理分页和物理分页参数

    Mybatis的插件运行原理,怎么编写一个插件

    • Mybatis只能编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四种接口插件
    package com.mi.learn.mybatis.handler;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.*;
    import org.springframework.cglib.proxy.InvocationHandler;
    import org.springframework.cglib.proxy.Proxy;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    @Slf4j
    @Component
    @Intercepts({@Signature(type= Executor.class, method = "select", args = {MappedStatement.class,Object.class})})
    public class MybatisInterceptor implements Interceptor {
        public Object intercept(Invocation invocation) throws Throwable {
            Object target = invocation.getTarget(); //被代理对象
            Method method = invocation.getMethod(); //代理方法
            Object[] args = invocation.getArgs(); //方法参数
            // do something ...... 方法拦截前执行代码块
            Object result = invocation.proceed();
            // do something .......方法拦截后执行代码块
            return result;
        }
        public Object plugin(Object target) {
            return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    return intercept(new Invocation(target, method, args));
                }
            });
        }
    }
    
    

    Mybatis一对多、多对一

    • 一对多

      List<StudentExam> selectStudentExam();
      
      //StudentExam
      @Data
      public class StudentExam {
      
          private String name;
      
          private String code;
      
          private List<Exam> examList;
      
      }
      
      // Exam
      @Data
      public class Exam {
      
          private String code;
      
          private String subject;
      
          private String score;
      
      }
      
    ```xml
    <resultMap id="StudentExamResultMap" type="com.mi.learn.mybatis.bean.StudentExam">
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="code" jdbcType="VARCHAR" property="code" />
        <collection property="examList" ofType="com.mi.learn.mybatis.domin.Exam" column="code">
        <result column="code" jdbcType="VARCHAR" property="code"/>
        <result column="subject" jdbcType="VARCHAR" property="subject"/>
        <result column="score" jdbcType="VARCHAR" property="score"/>
        </collection>
    </resultMap>
    
    
    <select id="selectStudentExam" resultMap="StudentExamResultMap">
        select s.* , e.*
        from student s , exam e
        where s.code = e.code
        order by s.code
    </select>
    
    ```
    
    • 多对一

      List<ExamStudent> selectExamStudent();
      
      //StudentExam
      @Data
      public class StudentExam {
      
          private String name;
      
          private String code;
      
          private List<Exam> examList;
      
      }
      
      //Exam
      @Data
      public class Exam {
      
          private String code;
      
          private String subject;
      
          private String score;
      
      }
      
    ```xml
    <resultMap id="ExamStudentResultMap" type="com.mi.learn.mybatis.bean.ExamStudent">
        <result column="code" jdbcType="VARCHAR" property="code"/>
        <result column="subject" jdbcType="VARCHAR" property="subject"/>
        <result column="score" jdbcType="VARCHAR" property="score"/>
        <association property="student" javaType="com.mi.learn.mybatis.domin.Student">
        <result column="code" jdbcType="VARCHAR" property="code"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
        </association>
    </resultMap>
    
    <select id="selectExamStudent" resultMap="ExamStudentResultMap">
        select s.* , e.*
        from student s , exam e
        where s.code = e.code
        order by s.code
    </select>
    ```
    

    Mybatis延迟加载

    # 开启缓存
    mybatis.configuration.cache-enabled=true
    # 延迟加载
    mybatis.configuration.lazy-loading-enabled=true
    # 关闭积极加载
    mybatis.configuration.aggressive-lazy-loading=false
    
        List<StudentExam> selectLazyStudent();
    
        List<Exam> selectExamByCode(@Param("code") String code);
    
    
        @Test
        @Transactional
        public void selectLazyStudentTest(){
            List<StudentExam> res = mapper.selectLazyStudent();
            log.info(JSON.toJSONString(res));
            log.info("==========================================");
            List<Exam> examList1 = mapper.selectExamByCode("001");
            List<Exam> examList2 = mapper.selectExamByCode("002");
            List<Exam> examList3 = mapper.selectExamByCode("003");
            log.info("==========================================");
            log.info("examList1:{}",JSON.toJSONString(examList1));
            log.info("examList1:{}",JSON.toJSONString(examList2));
            log.info("examList1:{}",JSON.toJSONString(examList3));
        }
    
    <resultMap id="StudentExamLazyResultMap" type="com.mi.learn.mybatis.bean.StudentExam">
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="code" jdbcType="VARCHAR" property="code" />
        <association property="examList" javaType="List" select="com.mi.learn.mybatis.mapper.StudentMapper.selectExamByCode" column="code"/>
    </resultMap>
    
    <select id="selectLazyStudent" resultMap="StudentExamLazyResultMap">
        select s.*
        from student s
        order by s.code
    </select>
    
    <select id="selectExamByCode" resultType="com.mi.learn.mybatis.domin.Exam" parameterType="string">
        select * from exam where exam.code = #{code}
    </select>
    

    日志:

    com.mi.learn.mybatis.StudentMapperTest   : ==========================================
    com.mi.learn.mybatis.StudentMapperTest   : ==========================================
    com.mi.learn.mybatis.StudentMapperTest   : examList1:[{"code":"001","score":"80","subject":"数学"},{"code":"001","score":"90","subject":"语文"},{"code":"001","score":"90","subject":"英语"}]
    com.mi.learn.mybatis.StudentMapperTest   : examList2:[{"code":"002","score":"75","subject":"数学"},{"code":"002","score":"85","subject":"语文"},{"code":"002","score":"85","subject":"英语"}]
    com.mi.learn.mybatis.StudentMapperTest   : examList3:[{"code":"003","score":"80","subject":"英语"}]
    

    并没有打印查询Exam表的sql语句

    Mybatis中xml的id是否能一样

    • 查找是按namespace+id来作为Map<key,MappedStatement>的key
    • 设置了namespace,namespace不一样,id一样是可以的
    • 没设置namespace,id不能一样。

    Executor

    • SimpleExecutor
      • 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象
    • ReuseExecutor
      • 执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
    • BatchExecutor
      • 执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

    Mybatis映射Enum枚举

    SexEnum

    public enum SexEnum {
    
        SEX_NO_KNOW(0,"未知"),
    
        SEX_ENUM_MAN(1,"男"),
    
        SEX_ENUM_WOMAN(2,"女");
        
        private Integer sex;
        private String context;
    
        SexEnum(int sex, String context) {
            this.sex = sex;
            this.context = context;
        }
    
        public void setSex(Integer sex) {
            this.sex = sex;
        }
    
        public Integer getSex() {
            return sex;
        }
    
        public void setContext(String context) {
            this.context = context;
        }
    
        public String getContext() {
            return context;
        }
    
    }
    

    Afinfo

    @Data
    @ToString
    public class Afinfo implements Serializable {
        private Integer id;
    
        private String name;
    
        private Integer age;
    
        private Date birth;
    
        private SexEnum sex;
    
        private String sexContext;
    
    
        private String memo;
    
        private static final long serialVersionUID = 1L;
    }
    

    AfinfoMapper.xml

    <resultMap id="BaseResultMap" type="com.mi.learn.mybatis.domin.Afinfo">
        <result column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="age" jdbcType="INTEGER" property="age" />
        <result column="birth" jdbcType="DATE" property="birth" />
        <result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.mi.learn.mybatis.myenum.SexEnum" />
        <result column="memo" jdbcType="VARCHAR" property="memo" />
      </resultMap>
    

    Test

    List<Afinfo> afinfoList11 = mapper.selectAfinfoWithMap(map);
    afinfoList11.forEach(item->{
        SexEnum sexEnum = item.getSex();
        log.info(sexEnum.getContext());
    });
    

    log日志

    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 女
    com.mi.learn.mybatis.AfinfoMapperTest    : 未知
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    com.mi.learn.mybatis.AfinfoMapperTest    : 男
    

    数据库中的数据

    sex
    1
    1
    1
    1
    1
    2
    0
    1
    1
    1
    
    1. 在ResultMap中加入固定的typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
    2. 加入javaType="com.mi.learn.mybatis.myenum.SexEnum",SexEnum为Enum包
    3. Afinfo的sex属性改成SexEnum引用对象

    使用

    pom.xml

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.23</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <version>2.4.4</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.1</version>
            </dependency>
    

    配置自动生成代码

        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
    
                <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.3.7</version>
                    <dependencies>
                        <dependency>
                            <groupId>mysql</groupId>
                            <artifactId>mysql-connector-java</artifactId>
                            <version>8.0.12</version>
                        </dependency>
                        <dependency>
                            <groupId>org.mybatis.generator</groupId>
                            <artifactId>mybatis-generator-core</artifactId>
                            <version>1.3.7</version>
                        </dependency>
                    </dependencies>
                    <executions>
                        <execution>
                            <id>MyBatis Artifacts</id>
                            <phase>package</phase>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <!--允许移动生成的文件 -->
                        <verbose>true</verbose>
                        <!-- 是否覆盖 -->
                        <overwrite>true</overwrite>
                        <!-- 自动生成的配置 -->
                        <configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
                    </configuration>
                </plugin>
            </plugins>
    
        </build>
    

    generatorConfig.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
        <!--加载配置文件,为下面读取数据库信息准备-->
        <properties resource="application.properties"/>
    
        <!--defaultModelType="flat" 大数据字段,不分表 -->
        <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
            <property name="autoDelimitKeywords" value="true" />
            <property name="beginningDelimiter" value="`" />
            <property name="endingDelimiter" value="`" />
            <property name="javaFileEncoding" value="utf-8" />
            <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
    
            <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
    
            <!-- 注释 -->
            <commentGenerator >
                <property name="suppressAllComments" value="true"/><!-- 是否取消注释 -->
                <property name="suppressDate" value="false" /> <!-- 是否生成注释代时间戳-->
            </commentGenerator>
    
            <!--数据库链接地址账号密码-->
            <jdbcConnection driverClass="${spring.datasource.driver-class-name}"
                            connectionURL="${spring.datasource.url}"
                            userId="${spring.datasource.username}"
                            password="${spring.datasource.password}">
            </jdbcConnection>
    
            <!-- 类型转换 -->
            <javaTypeResolver>
                <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
                <property name="forceBigDecimals" value="false"/>
            </javaTypeResolver>
    
            <!--生成Model类存放位置-->
            <javaModelGenerator targetPackage="com.mi.learn.mybatis.domin" targetProject="src/main/java">
                <property name="enableSubPackages" value="true"/>
                <property name="trimStrings" value="true"/>
            </javaModelGenerator>
    
            <!-- 生成mapxml文件 -->
            <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources/" >
                <property name="enableSubPackages" value="false" />
            </sqlMapGenerator>
    
            <!-- 生成mapxml对应client,也就是接口dao -->
            <javaClientGenerator targetPackage="com.mi.learn.mybatis.mapper" targetProject="src/main/java" type="XMLMAPPER" >
                <property name="enableSubPackages" value="false" />
            </javaClientGenerator>
    
            <table tableName="afinfo" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
                <generatedKey column="id" sqlStatement="Mysql" identity="true" />
            </table>
        </context>
    </generatorConfiguration>
    

    application.properties

    # 端口
    server.port=8001
    
    # xxxMapper.xml文件的路径
    mybatis.mapper-locations=classpath:mapping/*.xml
    # 日志显示sql
    logging.level.com.mi.learn.mybatis.mapper=debug
    
    # mysql数据库用户名
    spring.datasource.username=root
    # mysql数据库用户密码
    spring.datasource.password=123456
    # mysql连接url
    spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    # mysql连接驱动
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    

    文档

    Mybatis官网

    Mybatis面试题

    相关文章

      网友评论

          本文标题:Mybatis

          本文链接:https://www.haomeiwen.com/subject/esuurrtx.html