Mybatis总结

Mybatis

Mybatis的介绍

mybatis是Apache软件基金会下的一个开源项目,前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下,改名为mybatis。2013年11月又迁移到了github。

是持久层框架,用来访问数据库,只是众多持久层框架中一个,也是ORM框架:对象关系映射框架。

mybatis的优点

  1. 简单易学:mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件即可。
  2. 使用灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。SQL语句写在XML里,便于统一管理和优化。
  3. 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易进行单元测试。SQL语句和代码的分离,提高了可维护性。

mybatis的不足

  1. 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
  2. SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
  3. 框架还是比较简陋,功能尚有缺失,二级缓存机制不佳

ORM映射机制(Object Relational Mapping 对应关系映射)

什么是ORM?

将关系型数据库映射成面向对象编程的语言,如果使用框架,这由框架去完成。

mybatis主要采用的技术是映射,通过映射的方式传递信息由框架进行解析,然后与数据库进行交互最后完成对数据集的持久化操作。

Mybatis的两种映射方式

java与数据库表所对应的关系:

​ 类 -------> 表结构

​ 对象------>记录

​ 类成员---->字段名

1.XML配置文件

核心配置文件

​ 核心文件本质上在mybatis的底层会创建出一个configuration的实体类,该配置文件有很多标签,各个标签的位置不可变,必须从上到下排列,可以省略。下列仅列举常用标签顺序

properties

​ 设置外部属性文件(properties),以替代其他位置中的值,${文件中的key}

​ properties标签中也有url属性,连接其他地址的配置文件

如果是用第二种写法,那么在对应的调用出要使用${property标签中的name}

有两种写法,如下

<!--引入数据库连接的配置文件-->
//第一种写法
<properties resource="jdbc.properties"/>  
//第二种写法
<properties>
    <!--在文档内定义键和值,文件要放在src否则找不到路径-->
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
    <property name="jdbc.url" value="jdbc:mysql://localhost:3306/day25"/>
    <property name="jdbc.username" value="root"/>
    <property name="jdbc.password" value="root"/>
</properties>

注意: 当两种方式都使用的时候,以resource的文件内容为主

​ 执行流程是先执行内容的property标签再执行resource,所以会进行覆盖。

settings

设置其他文件信息 如log4J的配置信息

<!--配置LOG4J-->
<settings>
    <setting name="logImpl" value="log4j"/>
</settings>
typeAliases

​ 为实体类设置别名,如果不配置该属性则所有类均必须使用全限定类名作为查找依据

​ 有两个属性标签,package和typeAliase 一个是给整个包下的所有类设置别名,一个是为单个类设置别名,可以存在多个子标签

​ package标签中的属性是name

​ typeAlias标签中的属性是type

<!--起别名-->
<typeAliases>
    <package name="com.itheima.bean"/>
    <typeAlias type="com.itheima.bean.Classes"/>
    <typeAlias type="com.itheima.bean.Course"/>
</typeAliases>

​ 在mybatis中已经定义很多别名,我们可以直接使用

  • 内置别名

    别名是小写的,以下类型可以直接使用

parameterType="java.lang.Integer" 换成 parameterType="int"
别名真实类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

说明:

  • 内置别名可以直接使用

  • 别名不区分大小写

  • 自定义类的别名就是类名

plugins

​ 额外插件时需要进行配置的标签,如使用插件标签PageHelper的时候

<!--集成分页助手插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
environments

对JDBC连接信息进行设置,指定连接池和事务管理器

  1. transactionManage标签 事务管理器

    type可以指定为

    1. JDBC(使用JDBC的事务)
    2. Managed(使用Spring,web容器)
  2. dataSource标签 数据源信息 (连接池)

​ type可以指定为

  1. Pooled 使用

  2. Unpooled 不使用

  3. JNDI 用名字获取web中资源

    3.可以有多个environment标签来指定多个数据库信息

​ 但是最终使用的数据库以environments标签中default=“id名”的哪一个为准

<!--environments配置数据库环境,环境可以有多个。default属性指定使用的是哪个-->
<environments default="mysql">
    <!--environment配置数据库环境  id属性唯一标识-->
    <environment id="mysql">
        <!-- transactionManager事务管理。  type属性,采用JDBC默认的事务-->
        <transactionManager type="JDBC"></transactionManager>
        <!-- dataSource数据源信息-->
        <dataSource type="POOLED">
            <!-- property获取数据库连接的配置信息 -->
            <property name="driver" value="${driver}" />
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
        </dataSource>
    </environment>
</environments>
mappers

有两种方式进行加载映射配置文件,在此标签下可以对多个包或者文件进行加载

  • 方式一:加载单个映射文件mapper
  • 方式二:包扫描加载映射文件package
    <!-- mappers引入映射配置文件 -->
    <mappers>
            <!--
    子元素:mapper
        属性:
        resource:加载类路径下指定的配置文件,注:分隔符是/,而不是点号
        url: 读取指定路径下配置文件,或者网络的配置文件
        <mapper url="file:///d:/UserMapper.xml"/>
        class: 指定接口的完全限定名,用于注解的配置,不需要XML文件。
        <mapper class="com.itheima.dao.UserMapper"/>

    子元素:package
        1. 指定扫描哪个包下所有的DAO接口,如果使用这种写法,接口名与配置文件名字要相同。
        如:接口名UserMapper.java 配置文件名:UserMapper.xml
        2. 接口与配置文件必须放在同一个包下
    -->
       
        <!-- mapper 引入指定的映射配置文件   resource属性指定映射配置文件的名称 -->
        <mapper resource="StudentMapper.xml"/>  //放在src下时,所指定的xml配置文件
        <mapper class="com.itheima.dao.UserMapper"/> //指定接口,用.隔开。注解时直接用;配置文件时,配置文件要在该接口文件下 
        <package name="com.itheima.dao"/> //包名的全限定名,采用配置文件时,配置文件名字与接口名要相同
    </mappers>

2.映射配置文件

明确:

全限定名

根标签mapper的设置 name命名空间必须为对应的接口的

动态代理技术

mybatis的底层是使用的动态代理技术,所以才可以通过我们指定的接口,找到对应的方法进行执行再返回数据给调用处。

在使用mybatis框架的时候,主要是对接口和映射配置文件进行操作。

事务的提交

在mybatis中,默认是手动提交的,对增删改进行操作的时候,如果不开程序没有错误的时候就不会报任何错误。也不会出现任何提示。数据库不会有任何更改。

说明:如果在同一个方法中,有多个DML数据库操作,需要使用手动提交的方式。

在mybatis的核心类工厂中提供了重量级,

自动提交:

SqlSession sqlSession = sqlSessionFactory.openSession(true);

手动提交需要自己进行提交或者回滚操作,运用场景与转账一致:

sqlSession.commit();
sqlSession.rollback();
占位符

写SQL语句的时候,由于JDBC使用的是preparedStatement,所以在指定参数的时候必定有占位符,mybatis提供的占位符为

#{名字随意但要有意义},但是如果传递进来的参数为实体类时,那么#{}中必须填实体类对象的成员变量名

当sql进行解析后自带‘ ’ ,不需要再写‘ ’,如:

UPDATE student SET name = #{name},age = #{age} WHERE id = #{id} ;

在sql中的写法:UPDATE student SET name = ‘ 张三 ’,age = ‘ 23 ’ WHERE id = 2;

还提供${value}进行解析后不带‘ ’ ,此方法有SQL注入问题且变量名必为value。

映射文件的三种输入类型

主要针对parameType的填写

  1. 简单类 八大基本类型具体书写格式可参照typeAliases的别名规范

  2. POJO类(javaBean)

    • 实体类

      自己写的单一类 例如User,Student等,主要用于单表查询

      当传入的是实体类的时候,必使用实体类的名字而不是随意乱写

    • 实体类中含实体类

      自己写的类grade中含有多个其他类作为成员变量存在 如:user ,string ,int

      此时若传入类型resultType=grade ,那么在sql语句中应该用user.user的成员变量来进行调用

      如:select * from student where name like ‘%’ #{user.name} ‘’%’

映射文件的两种输出类型

主要针对resultType的填写

  1. 简单类 八大基本类型
  2. 实体类 在接口处使用集合来进行封装的时候 ,配置文件中填写泛型的类型,底层自动封装为集合返回

小结

  1. 接口与xml映射配置文件要放在同一包下,接口名与配置文件名要一致

    满足这个条件后,mappers中才可以class,resource属性,package标签通用,使用注解方式优先使用package标签扫描整个包

  2. 配置文件的命名空间要使用接口的全限定类名

  3. 增删改时要提交事务 opensession(true)。

  4. 传入的参数为POJO的第二种形态时要使用对象。成员变量为sql的占位符进行赋值

  5. 接口的参数和配置文件中定义规则

    接口中的名字=配置标签中的id

    接口中的形参=parameterType

    接口中的返回值=resultType/resultMap

    接口中的方法名不可有重载方法

  6. 核心配置文件的顺序必须由上到下依次排列(不用的省略掉)

Mybatis开发

增删改

增删改的标签是可以进行通用的,为何可以通用而不会报错?因为底层逻辑是增删改都使用了同一套执行流程,对传进来的语句和占位符进行赋值,然后直接返回。(具体在JDBC文档中查看对mybatis底层的框架模板)

增删改查的主要标签的各个含义

  • id 该标签的名字,通过该id名查找或者调用
  • resultType/resultMap 执行语句之后返回的结果集
  • parameterType 调用此方法时前方传来的数据类型 可以省略

    • 提供子标签 selectKey

      作用是在插入一条语句的同时,可以获取一个该对象插入之后的主键值。

      • 注解
        /**
         * 添加用户
         * @SelectKey 获取新添加的记录主键值
         * 属性:
         *  keyColumn:表中主键列名
         *  keyProperty:实体类中主键的属性名
         *  resultType: 主键返回的数据类型
         *  before: true表示在insert语句前执行,false表示在insert语句后执行
         *  statement: 要执行的SQL语句
         */
        @Insert("insert into user values(null, #{username},#{birthday},#{sex},#{address})")
        @SelectKey(statement = "select last_insert_id()" ,keyColumn = "id", keyProperty = "id", resultType = Integer.class, before = false)
        int insertUser(User user);
        
        • xml

          两种方式

    1. 子标签selectKey
      2. 在insert标签中 调用属性useGeneratedKeys=“true” (oracle不支持)

      其他标签不支持该功能,因为其他标签是可以通过查询来得到对应的主键值的。

      注意:

      1. 在插入语句之后就要马上执行该子标签

      2. 子标签的各个参数含义

        selectKey :获取新加的主键值
        keyColumn: 表中主键列名
        keyProperty:实体类中主键属性名
        resultType: 主键的数据类型,int类型
        order:在insert执行前,还是执行后执行这个查询
        标签体:要执行的查询语句

        3.通过对象的getId()得到新增的主键值

    <insert id="insert" parameterType="student">
        INSERT INTO student VALUES (null,#{name},#{age})
        <selectKey keyColumn="id" resultType="int" keyProperty="id" order="AFTER" >
            select last_insert_id();  //这个方法是数据集的聚合函数方法 
        </selectKey>
    </insert>
    
    <!--
    parameterType可以省略
    如果参数是实体类,花括号中是属性名:#{属性名}
    keyProperty:实体类中主键属性名
    keyColumn:表中主键列名
    useGeneratedKeys: 使用生成的主键
    使用这种方式的前提是:数据库本身有主键自动增长的功能,如:mysql, sql server。注:Oracle没有
    -->
    <insert id="addUser" parameterType="user" keyProperty="id" keyColumn="id" useGeneratedKeys="true">
        insert into user values (null,#{username},#{birthday},#{sex},#{address})
    </insert>
    
    System.out.println(student.getId());
    
  • select

使用配置文件查询

注:

​ 1.自动封装类名与字段名

​ resultType是默认自动封装,如果名字不符无法封装。

​ 在resultMap中,设置标签autoMapping=true 自动封装,如果名字不符无法封装。

​ 当类名是驼峰命名,表名是标准命名,可以开启核心配置文件中的mapUnderscoreToCamelCase

​ 如: 类中变量 userName 字段名 user_name (sql中的命名规范)就可以在不指定名字的情况下进行自动映射

    <settings>
        <!--映射下划线为驼峰命名法,会自动将create_time对应createTime-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
           <!--log4j-->
        <setting name="logImpl" value="log4j"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

​ 2.使用resultMap的时候,必定有一个类中存在需要查询的所有字段或者间接拥有(实例对象/集合<实例对象>),否则无法达到理想查 询目标,如果查询类中存在对应字段,但是没有使用resultMap映射,则输出时对应成员变量为null。

​ 3.resultMap进行封装的时候,实际上是通过对应的表查询出来的id来封装的

  • 单表查询
    单表查询使用 resultType

    因为该属性的作用是将结果集直接封装到对应的jabaBean中去

    使用任何一种查询,只要使用resultType则是单表查询,此处不做任何赘述,类中名字不对不会封装

  • 多表查询

    多表查询使用resultMap

    因为该属性的作用是按照指定类(type属性)中各个变量名对应进行封装,多表查询列名不确定,故需要指定。

    多表查询的一对一,一对多,多对多都需要指定resultMap进行封装

    此处有两种方式,第一种更为简便。

​ 1.使用一个resultMap进行整合,主类中有多个成员变量,则有多个collection或association标签

​ 若autoMapping=“true”,相同字段名同样需要一一映射(不建议开,影响可读性)

使用到的关键属性名 ofType 指定property确定的成员变量的数据类型

//核心配置文件
<mappers>
    <mapper resource="com/itheima/one_to_many/OneToMany.xml"/>
</mappers>
//接口
public interface OneToManyMapper {
    //查询全部
    public abstract List<Classes> selectAll();
}
<mapper namespace="com.itheima.table01.OneToOneMapper">
    <!--
    id:表示接口中方法名
    resultType:表示方法返回的数据类型,如果返回的是集合,这里指定集合中每个元素类型
    select标签标签体就是SQL语句
    -->
    <resultMap id="selectaaaaaa" type="Card" autoMapping="true">
    <id property="id" column="cid"/>
        <!--开了autoMapping="true"就会自动映射相同名字的-->
    <!--<result property="number" column="number"/>-->
        
    //下面是card类中的 实例对象 Person p
    <association property="p" javaType="Person" autoMapping="true">
    <id property="id" column="pid"/>
    <!--<result property="name" column="name"/>-->
    <!--<result property="age" column="age"/>-->
    </association>courses
       //下面是card类中的 集合对象 list<course> courses
    <collection property="courses" ofType="Course">
        <id property="id" column="cid"/>
        <result property="name" column="cname" />
    </collection>
    </resultMap>

    <select id="selectAll" resultMap="selectaaaaaa" >
        SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id
    </select>
</mapper>

​ 2.使用虚拟表resultMap进行映射

	虚拟表指向主表,标签体指向从表(此处只有一个从表,如有多个就分别指向每一个从表)

使用到的关键属性名 extends 虚拟表是继承哪一个主表

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间,指定接口的完全限定名字-->
<mapper namespace="com.itheima.table02.OneToManyMapper">
    <!--
    id:表示接口中方法名
    resultType:表示方法返回的数据类型,如果返回的是集合,这里指定集合中每个元素类型
    select标签标签体就是SQL语句
    -->
    <!--映射class主表-->
    <resultMap id="c" type="Classes">
        <id property="id" column="cid"/>
        <result property="name" column="cname"/>
    </resultMap>

    <!--映射student从表-->
    <resultMap id="s" type="student" >
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="age" column="sage"/>
    </resultMap>

    <!--映射两者的虚拟表-->
    <resultMap id="cs" type="Classes" extends="c">
        <collection property="students" resultMap="s"/>
    </resultMap>

    <select id="selectAll" resultMap="cs">
        SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid
    </select>
</mapper>

使用注解查询

没有映射配置文件,仅在核心配置文件mappers标签中指定对应接口即可。

<mappers>
        <mapper class="com.heima.dao.StudentMapper"/>   可扫描单个接口
        <package name="com.heima.dao"/>			按包扫描
</mappers>
  • 单表查询

    可以为@Results 取名字,以复用 使用 id=

    value 参数是一个@Result数组,每个@Result对象反应一个列的映射

    如果只有一个value属性值,value的名字可以省略

  • /**
     * DAO接口
     */
    public interface UserMapper {
        /**
         * 通过id查询用户的方法
         */
        @Select("select * from user where id=#{id}")
        User findUser(int id);
    
        /**
         * 查询user2的表,其中user_name是对不上属性名的username
         * @Select 注解用于查询
         * @Results 注解用于映射不同的列和属性名
         *  属性:id用来定义这个映射的名字,供其它查询语句重用
         *  属性:value 参数是一个@Result数组,每个@Result对象一个列的映射
         * @Result 用来映射每一列
         *  属性:column 指定列名
         *  属性:property 指定属性名
         */
        @Select("select * from user2 where id=#{id}")
        @Results(id = "userMap",
                value = { @Result(column = "user_name",property = "username") })
        User findUser2(int id);
    
        /**
         * 查询user2表中所有的记录
         * @ResultMap 用来引用上面定义的映射
         */
        @Select("select * from user2")
        @ResultMap("userMap")
        List<User> findAllUsers2();
    }
    
public interface StudentDao {
    @Select("select * from student")
    List<Student> selectAll();

    @Delete("delete from student where id=#{id}")
    int delete(Integer id);
    
    @insert("插入语句")
    int a(Student stu);
    
    @update("更新语句")
    int up(Student stu);
}
  • 多表查询

    注意:

    1. @Results({})是一个数组,所以中间每个result要用,隔开
    2. javaType = Person.class 可以省略
    3. 主表成员变量与表的字段同名可以不指定,否则必须指定
    4. 主表的查询语句可以是单表查询也可以是表连接查询,只要最终与预期查询的表结果一致即可
    5. one/many的取值是根据 主类中的成员变量是集合还是实体类
    6. 注解中的主类不需要指定,默认以返回值(泛型/实体类)为主类
    7. 传递的方法不局限于同一个接口中,可以指定对应接口的全限定名.其中的方法调用也行
    public interface CardMapper {
    @Select("select * from card ")
    @Results({
            @Result(property = "id",column = "id"),//同名可以不指定
            @Result(property = "number",column = "number"),//同名可以不指定
            @Result(property = "p",	  //主类中的变量名   主要目的是为成员变量P进行封装
                    column = "pid",   //往下传递的参数
                    javaType = Person.class,  //主类的数据类型
                    one=@One(select = "selectById"))  //参数所要传递到的方法处
       				// many=@Many(select = "com.heima.mapper.StudentMapper.selectById")) 调用其他接口中的方法
        	...   // 如果有多个方法需要调用,则继续@Result
            })
    public List<Card> selectAll();  //默认以返回值的类型进行封装
    
     @Select("select * from person where id=#{id}")  //除主表外,其余表都是单表查询以查询对应字段
     public Person selectById(Integer id);
        
    }
    

延时加载

什么是延迟加载,为什么要使用延迟加载?

概念:如果一张表关联了另一张表的数据,只加载这张表中的数据,它关联的另一张表中的数据等到需要用的时候才去加载,称为延迟加载,也叫懒加载。

在使用表关联查询的时候,是一次性把所有关联的多张表的数据全部查询出来,并且封装到对象中。如:1对1或1对多。

多表关联查询缺点:

  1. 查询速度更慢

  2. 更加占用对象的资源,还可能造成资源的浪费。

延迟加载使用指南
  1. 映射配置文件中使用,必须使用第二种虚拟表的方式进行映射

前提:一个类中含有多个成员变量且指向不同的类。如果不使用延迟加载则在查询的时候会自动全部查询出来,而不是按需查询

一对一执行流程

在这里插入图片描述

一对多执行流程
在这里插入图片描述

  1. 注解中使用

    首先要开启延迟加载

/**
 通过id查找用户
 @Result 用来映射一个列和属性名
 属性:property 指定另一方在实体类中属性名
 属性:column 指定为当前表中主键
 属性:one 表示1对1的关联
 @One注解:
 属性:select 指定下一条要执行的SQL语句方法名
 属性:fetchType 指定是否使用延迟加载 FetchType.LAZY 表示延迟加载  FetchType.EAGER 表示即时加载
 */
@Select("select * from user where id=#{id}")
@Results({
        @Result(property = "userInfo", column = "id", one=@One(select = "findUserInfoById", fetchType = FetchType.LAZY))
})
User findUserById(int id);

/**
 通过id查询用户扩展信息
 */
@Select("select * from user_info where id=#{id}")
UserInfo findserInfoById(int id);
开启延迟加载
<settings>
<!--映射下划线为驼峰命名法,会自动将create_time对应createTime-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

Mybatis核心类的使用及其执行流程

mybatis的三个核心类
  1. SqlSessionFactoryBuilder 召集工人 build()

  2. SqlSessionFactory(传入输入流) 开始造厂 重量级,极其消耗资源,建议设为静态成员只加载一次 openSession(true)

  3. SqlSession 开始营业

    需要在使用前读入核心配置文件

    读入核心配置文件的三种方式

//第1种,mybatis提供
InputStream resourceAsStream = Resources.getResourceAsStream("MybatisConfig.xml");
//第2种
InputStream resourceAsStream = 类名.Class.getResourceAsStream("MybatisConfig.xml");
//第3种
InputStream resourceAsStream = 对象名.getClass().getResourceAsStream("MybatisConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
mybatis的执行流程

使用框架核心类获取mapper对象—> 读取核心配置文件—>找到映射配置文件

​ —>mapper对象调用方法—>接口中的对应方法—>配置文件对应方法—>连接数据库进行增删改查—>返回数据

​ —>配置文件 —>接口中对应的方法 —>mapper调用处

重点
  • 定义接口中的方法,每个方法都必须是唯一的不可重载 返回值类型,传递参数,方法名

  • 映射配置文件中按增删改查功能的不同定义标签,标签中按各个属性,定义接口中的 返回值类型,传递参数,方法名

    1. 只有查询时,在resultType该属性中才存在返回一个POJO(javaBean[实体类,实体类中的实体类]),其余情况都是int类型
    2. 接口方法中定义的返回类型是List集合,那么配置文件中不可写list,应该写泛型类型。
    3. 在传值入SQL语句中的值#{}的赋值标准:基本类可以随便取名;实体类必须是实体类的成员变量名;实体类中的实体类则要用实体类的成员变量名.实体类中实体类的成员变量名

注:

​ 1.由于每次使用mybatis的时候都需要创建mapper对象,可以提取出来作为工具类进行调用。

​ 2.Mybatis会在底层通过接口的全限定名去调用底层方法selectList/One 方法执行对应的每一个方法,所以需要在配置文件中写接口的全限定名

SQL构建器(了解)

使用注解方式时,Myabatis提供一些额外的方式对注解中的SQL语句进行整合

注意:

​ 1.类中必须使用构造代码块进行调用

​ 2.该方法返回值需要使用String接收,同理在SQL()之后要toString 转为字符串格式

​ 3.注解中使用的是XXXProvider(type= XXX.class, method=“对应被调用方法名”)

package com.heima.bean;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.jdbc.SQL;

public class SqlConstracture {

    public  String select(){
    return   new SQL() {
            {
                SELECT("*");
                FROM("student");
            }
        }.toString();
    }

    public static String delete(){
        return new SQL(){
            {
                DELETE_FROM("STUDENT");
                WHERE("id=#{id}");
            }
        }.toString();

    }
}
public interface StudentDao {

    //    @Select("select * from student")
    @SelectProvider(type =SqlConstracture.class,method ="select" )
    List<Student> selectAll();

    //    @Delete("delete from student where id=#{id}")
    @DeleteProvider(type = SqlConstracture.class,method ="delete" )
    int delete(@Param("id") Integer id);

}

小结

  • 当类成员变量与字段名相同时,注解不需要指定,配置文件需要指定

如遇到驼峰命名和字段名_重合时,可以在settings里开启核心配置文件中的mapUnderscoreToCamelCase 来自动映射

  • 接口的方法不可以重载(方法唯一)

动态SQL

在需要使用动态SQL的时候,都在配置文件中执行,如果使用注解的方式写动态SQL没有可读性

提供动态SQL的标签有

if,where,set,foreach,sql,include

不是独立关系,可以进行相互匹配只要逻辑上行得通即可,使用了这个标签就不需要再写对应位置上的关键字

if和where标签

if标签的作用: 对传递进来的数据进行逻辑判断,如果符合条件就进行拼接

where标签的作用:在where标签中进行多个if标签的判断的时候,可以自动去除在语句中存在的多余的and or where关键字

​ 需要注意的是只能自动去除,不会自动添加,少了关键字依然报错

​ 为了增强代码的健壮性,所有if判断的前面都添加对应关键字

<select id="selectCondition" resultType="student" parameterType="student">
   select * from student
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>
set标签

set标签的作用:主要用于update标签,来设置set关键字,以去除更新多个语句时多余存在的 ,

为了增强代码的健壮性,所有if判断的前面都添加,

<!--
1. 如果username属性不为空,则更新这个字段。
2. 如果sex不为空则更新这个字段
-->
<update id="updateUser">
    update user
    <set>
        <if test="username!=null and username!=''">
            username=#{username},
        </if>
        <if test="sex!=null and sex!=''">
            sex=#{sex},
        </if>
    </set>
    where id=#{id}
</update>
foreach标签

foreach标签的作用:对传入进来的参数进行遍历循环再输出到指定的位置上

foreach标签的属性作用:用于遍历集合或数组,多次拼接生成SQL语句
collection取值:如果是集合使用list,如果是数组使用array
item代表集合中每个元素,给元素定义一个名字
separator每次遍历完成以后添加1个分隔符
#{变量名.属性}标签体中引用的时候,必须使用变量名.属性名

总共有三种遍历对象: 集合,数组,要遍历集合或者数组在某个resultMap或者JavaBea中

  1. parameterType为集合

    <!--使用foreach标签遍历一个集合,生成SQL语句-->
    <insert id="addUsers" >
        insert into user (username, birthday, sex, address) values
        <!--
        foreach标签的属性:
        collection 指定是集合还是数组,如果是集合使用list,如果是数组使用array
        item 表示集合中每个元素的变量名,这里是一个实体类对象
        separator 每次拼接完以后,添加1个符号分隔
        -->
        <foreach collection="list" item="user" separator=",">
            (#{user.username},#{user.birthday},#{user.sex},#{user.address})
        </foreach>
    </insert>
    
  2. parameterType为数组

    <!--批量删除用户 -->
    <delete id="deleteUsers">
        delete from user where id in
        <!--
        collection: 表示要遍历的集合类型,数组使用array
        item: 表示其中每个元素的变量名
        open:表示循环开始前添加一个符号,只会执行1次
        separator 每次拼接完以后,添加1个符号分隔,执行多次
        close:表示循环结果以后添加一个符号,只会执行1次
        -->
        <foreach collection="array" open="(" item="id" separator="," close=")">
            #{id}
        </foreach>
    </delete>
    
  3. parameterType为JavaBean ,成员变量为数组/集合

    <select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
        <include refid="defaultUser"></include>
        <where>
            <if test="ids != null and ids.size()>0">
                <foreach collection="ids" open="and id in (" item="uid" separator="," close=")">
                    #{uid}
                </foreach>
            </if>
        </where>
    </select>
    

注意: 当传入的集合或者数组里面存放的是一个一个的实体类的时候,在调用时必须使用#{实体类。成员变量}赋值

sql和include标签

sql标签的作用:提取重复代码到此标签中,

include标签的作用:在对应处通过refid调用sql标签id名

优缺点:在使用的时候会大大简化代码的编写,但是极其影响代码可读性,慎用。

<!--
1. sql标签定义查询条件代码块
    Map = {minDate='1999-9-9', maxDate='2000-1-1'}
    在XML中<或>符号是有特殊含义的,通常不能直接写在XML中
    有两种解决方案:
    1) 使用转义:&lt; &gt;
    2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析
 -->
<sql id="sqlCondition">
    <where>
        <if test="minDate!=null and minDate!=''">
            birthday >= #{minDate}
        </if>
        <if test="maxDate!=null and maxDate!=''">
            <![CDATA[
              and birthday <= #{maxDate}
            ]]>
        </if>
    </where>
</sql>

<!--
include标签的作用:引用使用sql标签定义的代码段
   属性:refid对应上面sql的id值
-->
<select id="findUsersByMap" resultType="user">
    select * from user  <include refid="sqlCondition"/>
</select>

<select id="findCountByMap" resultType="int">
    select count(*) from user <include refid="sqlCondition"/>
</select>

分页插件

PageHelper 可以自动在底层实现分页的效果,是一个三方jar包

分页原理:

package com.itheima.entity;

import java.util.List;

/**
 * 封装页面所有属性的实体对象
 * 使用泛型以后可以通用
 */
public class PageBean<T> {
    //第1组:可以从数据库中查询到的
    private List<T> data;  //这一页的数据
    private int count;   //一共有多少条记录

    //第2组:由用户在浏览器端提供的数据
    private int current;  //当前第几页
    private int size;  //每页大小

    //第3组:由其它的属性计算出来,写在get方法中
    private int first;   //第一页
    private int previous;  //上一页
    private int next;   //下一页
    private int total;   //总页数=最后一页

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getCurrent() {
        return current;
    }

    public void setCurrent(int current) {
        this.current = current;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    //计算第1页
    public int getFirst() {
        return 1;  //直接返回1
    }

    public void setFirst(int first) {
        this.first = first;
    }

    //计算上一页
    public int getPrevious() {
        //如果当前页大于1,上一页=当前页-1,否则返回1
        if (getCurrent() > 1) {
            return getCurrent() - 1;
        }
        else {
            return 1;
        }
    }

    public void setPrevious(int previous) {
        this.previous = previous;
    }

    //计算下一页
    public int getNext() {
        //如果当前页小于最后一页,下一页=当前页+1,否则返回最后一页
        if (getCurrent() < getTotal()) {
            return getCurrent() + 1;
        }
        else {
            return getTotal();
        }
    }

    public void setNext(int next) {
        this.next = next;
    }

    //计算总页数 = 总记录数 / 页面大小
    public int getTotal() {
        //如果能整除就正好是这么多页,不能整除就加1
        if (getCount() % getSize() == 0) {
            return getCount() / getSize();
        }
        else {
            //在java中整数相除得到整数
            return getCount() / getSize() + 1;
        }
    }

    public void setTotal(int total) {
        this.total = total;
    }

    //注:因为有四个属性是在get方法中计算的,如果直接输出属性值,不一定准确,应该输出get方法才能看到正确的结果
    @Override
    public String toString() {
        return "PageBean{" +
                "data=" + getData() +
                ", count=" + getCount() +
                ", current=" + getCurrent() +
                ", size=" + getSize() +
                ", first=" + getFirst() +
                ", previous=" + getPrevious() +
                ", next=" + getNext() +
                ", total=" + getTotal() +
                '}';
    }
}

主要针对所有的数据库进行分页

步骤:

  1. 导包

  2. 在核心配置文件中设置plugins

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
  3. 调用 PageHelper相关方法

    PageHelper.startPage(第几页,显示的个数); 分页显示,循环显示时仅显示指定个数,使用的位置要放在查询之前

    PageInfo info = new PageInfo<>(调用方法的结果集); 用于调用显示上一页等操作

    package com.itheima.paging;
    
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.itheima.bean.Student;
    import com.itheima.mapper.StudentMapper;
    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.InputStream;
    import java.util.List;
    
    public class Test01 {
        @Test
        public void selectPaging() throws Exception{
            //1.加载核心配置文件
            InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
    
            //2.获取SqlSession工厂对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    
            //3.通过工厂对象获取SqlSession对象
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
    
            //4.获取StudentMapper接口的实现类对象
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    
            //通过分页助手来实现分页功能
            // 第一页:显示3条数据
            //PageHelper.startPage(1,3);
            // 第二页:显示3条数据
            //PageHelper.startPage(2,3);
            // 第三页:显示3条数据
            PageHelper.startPage(3,3);
    
            //5.调用实现类的方法,接收结果
            List<Student> list = mapper.selectAll();
    
            //6.处理结果
            for (Student student : list) {
                System.out.println(student);
            }
    
            //获取分页相关参数
            PageInfo<Student> info = new PageInfo<>(list);
            System.out.println("总条数:" + info.getTotal());
            System.out.println("总页数:" + info.getPages());
            System.out.println("当前页:" + info.getPageNum());
            System.out.println("每页显示条数:" + info.getPageSize());
            System.out.println("上一页:" + info.getPrePage());
            System.out.println("下一页:" + info.getNextPage());
            System.out.println("是否是第一页:" + info.isIsFirstPage());
            System.out.println("是否是最后一页:" + info.isIsLastPage());
    
            //7.释放资源
            sqlSession.close();
            is.close();
        }
    }
    

Mybatis底层原理分析

此处去除所有安全判断之后,经过JDBC的底层,以注解为例分析可得:

这是JDBC底层调用反射框架时的参数

  @Test
    public void queryForObject() {
        //查询一条记录并封装自定义对象的测试
        String sql = "SELECT * FROM student WHERE sid=?";
        Student stu = template.queryForObject(sql,new BeanHandler<>(Student.class),1);
        System.out.println(stu);
    }

由此可得:需要用到SQL语句,对应接口的字节码对象,传入的形参。

  1. 当创建mapper实体对象的时候要读入核心配置文件,此时可以得到mappers标签中通过指定的配置文件可以得到所有接口的字节码对象(映射配置文件中有接口的全限定名)
  2. 接口的方法处可以获取到SQL语句,传入的形参和返回值对象(查看使用多对象list还是单对象实例或聚合函数)
  3. 通过验证使用selectList()/selectOne()在底层判断后,调用对应的单,多,聚合的反射方法。传入上述参数。
  4. 到达核心框架处,经过反射和元数据的判断后,执行SQL语句,把结果集对应封装进返回值对象(可以理解为使用了BeanUtils的populate方法),返回调用处
  5. 一一返回,到达调用处,结果框架调用。
其他零散知识点

​ 1.Data类中提供方法 valueOf(日期字符串) 可以将日期格式的字符串转为对应的Data类 以对应数据库中的Data属性

new User(null,“白龙马”,Date.valueOf(“2019-05-01”),“男”,“东海龙宫”);

2.在XML中<或>符号是有特殊含义的,通常不能直接写在XML中

	有两种解决方案:
1) 使用转义:&lt; &gt;
2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析
    <![CDATA[
         and birthday <= #{maxDate}
      ]]>

3.使用注解的方式,如果方法中的形参不使用map集合进行传递,那么多于一个形参时,使用@Param关键字

如下:

@Select("select * from student where id=#{id} and name=#{name}")
List<Student> selectAll(@Param("id") Integer id,@Param("name") String name);

由此可得:需要用到SQL语句,对应接口的字节码对象,传入的形参。

  1. 当创建mapper实体对象的时候要读入核心配置文件,此时可以得到mappers标签中通过指定的配置文件可以得到所有接口的字节码对象(映射配置文件中有接口的全限定名)
  2. 接口的方法处可以获取到SQL语句,传入的形参和返回值对象(查看使用多对象list还是单对象实例或聚合函数)
  3. 通过验证使用selectList()/selectOne()在底层判断后,调用对应的单,多,聚合的反射方法。传入上述参数。
  4. 到达核心框架处,经过反射和元数据的判断后,执行SQL语句,把结果集对应封装进返回值对象(可以理解为使用了BeanUtils的populate方法),返回调用处
  5. 一一返回,到达调用处,结果框架调用。
其他零散知识点

​ 1.Data类中提供方法 valueOf(日期字符串) 可以将日期格式的字符串转为对应的Data类 以对应数据库中的Data属性

new User(null,“白龙马”,Date.valueOf(“2019-05-01”),“男”,“东海龙宫”);

2.在XML中<或>符号是有特殊含义的,通常不能直接写在XML中

	有两种解决方案:
1) 使用转义:&lt; &gt;
2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析
    <![CDATA[
         and birthday <= #{maxDate}
      ]]>

3.使用注解的方式,如果方法中的形参不使用map集合进行传递,那么多于一个形参时,使用@Param关键字

如下:

@Select("select * from student where id=#{id} and name=#{name}")
List<Student> selectAll(@Param("id") Integer id,@Param("name") String name);

总结

  1. 核心配置文件的标签必须从上到下按顺序定义,否则报错
  2. 想要注解和配置文件mappers的子标签通用,必须接口与映射文件同名且在同一包下 class属性用.隔开,resource用/隔开
  3. 事务自动提交需要自己手动设置
  4. 核心配置文件中 resultMap有两种格式,单个map更为简单,虚拟表形式主要用于延迟加载时使用
  5. 对#{}的赋值,基本类名字随意,实体类是变量名,实体类中的实体类是实体类变量名.其成员变量名
  6. 用到动态SQL的时候都要用配置文件的形式,便于可读性
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值