MyBatis

举报
京与旧铺 发表于 2022/06/29 16:36:00 2022/06/29
【摘要】 MyBatis什么是框架?它是一个半成品软件,将所有的公共的、重复的功能解决掉,帮助程序快速高效的进行开发,它是可复用的、可扩展的。什么是MyBatis框架?MyBatis本是 apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis。2013年迁移到GitHub。MyB...

MyBatis

  • 什么是框架?

    • 它是一个半成品软件,将所有的公共的、重复的功能解决掉,帮助程序快速高效的进行开发,它是可复用的、可扩展的。

  • 什么是MyBatis框架?

    • MyBatis本是 apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis。2013年迁移到GitHub。

    • MyBatis完成数据库访问层的优化,它专注于sql语句,简化了过去JDBC繁琐的访问机制。

  • 代理模式

    • 无法访问目标对象,通过代理对象进行访问,而且增强式的访问,适合进行业务的扩展。

    • 功能:增强功能;限制目标对象的访问。

  • 代理模式的分类

    • 静态代理

    • 动态代理,又称为JDK动态代理,CGLib动态代理(子类代理)

  • 面向接口编程(重要)

    • 类中的成员变量设计为接口,方法的形参设计为接口,方法的返回值设计为接口,调用时接口指向实现类。

  • 动态代理

    • 代理对象在程序运行的过程中动态在内存构建,可以灵活的进行业务功能的切换。

    • JDK动态代理类型:class com.sun.proxy.$Proxy

  • 什么是静态代理

    • 特点:

      • 目标对象和代理对象实现同一个业务接口。

      • 目标对象必须实现接口。

      • 代理对象在程序运行前就已经存在。

      • 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理。(使用动态代理解决)

  • JDK动态代理

    • 目标代理对象必须实现业务接口(目标对象必须实现接口,才可以使用JDK的动态代理)

    • JDK代理对象不需要实现业务接口

    • JDK动态代理的对象在程序运行前不存在,在程序运行时动态的在内存中构建。

    • JDK动态代理灵活的进行业务功能的切换。

    • 本类中的方法(非接口中的方法)不能被代理。

  • CGLib动态代理

    • 又称为子类。动态的在内存中构建子类对象,重写父类的方法进行代理功能的增强。

    • 如果目标对象没有实现接口,则只能通过CGLib子类代理进行功能增强。

    • 子类代理是通过字节码框架ASM来实现的。

    • 注意:被代理类和方法不能使用final。

  • JDK动态代理用到的类和接口

    • Proxy类

      • 它是java.lang.reflect.Proxy包下的类。它有一个方法Proxy.newProxyInstance(...)专门用来生成动态代理对象。

        public static Object newProxyInstance(
                    ClassLoader loader,     //类加载器
                    Class<?>[] interfaces,  //目标对象实现的所有接口
                    InvocationHandler h     //代理的功能和目标对象的业务功能调用
                                        )
               throws IllegalArgumentException
            {...}
    • Method类

      • 反射用的类,用来进行目标对象方法的反射调用。

    • InvocationHandler接口

      • 它是实现代理和业务功能的,我们在调用时使用匿名内部实现。

      public Object getAgent(){
          return Proxy.newProxyInstance(
              //ClassLoader loader,类加载器,完成目标对象的加载
              target.getClass().getClassLoader(),
              //Class<?>[] interfaces,目标对象实现的所有接口
              target.getClass().getInterfaces(),
              //InvocationHandler h,实现代理功能的接口,匿名内部类实现
              new InvocationHandler(){
                  public Object invoke(
                      //创建代理对象
                      Object proxy,
                      //method就是目标方法
                      Method method,
                      //目标方法的参数
                      Object[] args) throws Throwable{
                          //代理功能
                          System.out.println("代理功能开始");
                          //主业务功能实现
                          Object obj = method.invoke(target,args);
                          //代理功能
                          System.out.println("代理功能结束");
                          return obj; //这个是目标方法的返回值
                      }
                  }
              )
      }
  • 面向接口编程

    • 类中的成员方法设计成接口

    • 方法的参数设计为接口

    • 方法的返回值设计为接口

    • 使用时使接口指向实现类

  • 什么是三层架构?

    • 界面层:和用户打交道的,接收用户的请求参数,显示处理结果。(jsp、html、servlet)

    • 业务逻辑层:接收了界面传递的数据,计算逻辑,调用数据库,获取数据。

    • 数据访问层:访问数据库,执行对数据的查询、修改、删除等。

    • 注意:各层之间的调用顺序是固定的,不允许跨层访问。

  • 三层对应的包及处理框架

    • 界面层:controller(servlet) --SpringMVC

    • 业务逻辑层:service(xxxService类) --Spring

    • 数据访问层:dao包(xxxDao类) --MyBatis

  • 三层中的架构

    • 用户使用界面层 -- 业务逻辑层 -- 数据访问层(持久层) -- 数据库(MySQL)

  • 为什么要使用三层?

    • 结构清晰,耦合度低,各层分工明确。

    • 可维护性高,可扩展性高。

    • 有利于标准化。

    • 开发人员可以只关心结构中的其中某一层功能的实现。

    • 有利于各层逻辑的复用。

  • SqlSessionFactory

    //读取mybatis-config.xml文件
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    //初始化mybatis,创建SqlSessionFactory类的实例
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    //创建SqlSession实例
    SqlSession session = SqlSessionFactory.openSession();


  • @Befor注解

    @Befor  //所有的@Test方法执行前执行的代码
     public void openSqlSession() throws IOException{
        //使用文件流读取核心配置文件sqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();
     }  
  • @After注解

    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }
  • 为实体类注册别名

    • 单个注册

      <typeAlias type="com.myself.pojo.Student" alias="stu"></typeAlias>
    • 批量注册

      <typeAliases>
          <package name="com.myself.pojo"/>
      </typeAliases>
  • 配置环境变量

    <!--配置环境变量-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"></transactionManager>
                <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>
  • 指定资源位置

    <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.xml</include>
                        <include>**/*.properties</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                        <include>**/*.properties</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>


  • 动态代理存在的意义

    • 实现了三个模块组的解耦,可以使它们可以在客户端各自可以独立的变化,为客户端的调用提供了极大的方便。通过这种方式,被代理的对象可以在运行时动态改变,需要控制的接口可以在运行时改变,控制的方式也可以动态改变,从而实现了非常灵活的动态代理关系。

  • 动态代理的实现规范

    • UsersMapper.xml文件与UsersMapper.java的接口必须在同一个目录下。

    • UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致。

    • UsersMapper.xml文件中标签的id值与UsersMapper.java的接口中方法的名称完全一致。

    • UsersMapper.xml文件中标签的parameterType属性值与UsersMapper.java的接口中的方法的参数类型完全一致。

    • UsersMapper.xml文件中标签的resultType值与UsersMapper.java的接口中的方法的返回值类型完全一致。

    • UsersMapper.xml文件中namespace属性必须是接口的完全限定名称com.myself.mapper.UsersMapper.

    • 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称com.myself.mapper.UsersMapper.

  • 设置日志输出

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
  • #{}占位符

    • 传参大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问,防止sql注入。

    • #{}里面如何写,看parameterType参数的类型:

      • 如果parameterType的类型是简单类型,则#{}里面随便写。

        <select id="getById" parameterType="int" resultType="users">  -->入参是简单类型
            select id,username
            from users
            where id=#{tzw}  -->随便写
        </select>
      • 如果parameterType的类型是实体类的类型,则#{}里面只能是类中成员变量的名称,而且区分大小写。

        <insert id="insert" parameterType="users">  -->入参是实体类
            insert into users(username)values(#{userName})
        </insert>
  • ${}字符串拼接或字符串替换

    • 字符串拼接,一般用于模糊查询中,建议少用,因为有sql注入的风险。

    • 里面如何写,也分两种情况:

      • 如果是简单类型,则${}里面随便写。

        <select id="getByName" parameterType="string" resultType="users">  -->入参是实体类
            select id,username
            from users
            where username like'%{tzw}%'  -->随便写
        </select>
      • 如果parameterType的类型是实体类类型,则${}里面只能是类中成员变量的名称。(现在很少用)

    • 优化后的模糊查询(以后都用这种方式):

      //parameterType只有实体类参要传入实体类类型,其它类型可以省略
      <select id="getByName" parameterType="String" resultType="users">
          select id,username
          from users
          where username like concat('%',#{name},'%')
      </select>
    • 字符串替换

      • 需求:模糊地址或用户名查询

        select * from users where username like '%小%';
        select * from users where address like '%市%'
        ​
        <--模糊用户名和地址查询-->
        <--如果参数超过一个,则parameterType不写-->
            <select id="getByNameOrAddress" resultType="users">
                select id,username,address
                from users
                where ${name} like concat('%',#{value},'%')
            </select>
  • 返回主键值

    • 在插入语句结束后,返回自增的主键值到入参的users对象的id属性中。

      <insert id="insert" parameterType="users">
          <selecKey KeyProperty="id" resultType="int" order="AFTER">
              select last_insert_id()   <--返回最近插入值自增的id值-->
          </selecKey>
          insert into users (username,address)values(#{userName},#{address})
      </insert>
    • selecKey标签的参数详解:

      • keyProperty:users对象的那个属性来接返回的主键值。

      • resultType:返回的主键的类型。

      • order:在插入语句执行前,还是执行后返回主键的值。(BEFER、AFTER)

  • UUID:这是一个全球唯一字符串,由36个字母数字加下划线组成。

    @Test
    public void testUUID(){
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid);
    }
  • 什么是动态sql:

    • 可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更为简单。

    • <sql>:用来定义代码片段,可以将所有的列名或复杂的条件定义为代码片段,供使用时使用。

    • <include>:用来引用<sql>定义的代码片段。

      //定义代码片段
      <sql id="allColumns">
          id,username,birthday,sex,address
      </sql>
      //引用定义好的代码片段
      <select id="getAll" resultType="users">
          select<include refid="allColumns"></include>
          from users
      </select>
    • <if>:进行条件判断

    • <where>:进行多条件拼接,在查询,删除,更新中使用

      <select id="getByCondition" parameterType="users" resultType="users">
          select<include refid="allColumns"></include>
          from users
          <where>
              <if test="userName != null and userName != ''">
                  and username like concat('%',#{userName},'%')
              </if>
              <if test="sex != null and sex != ''">
                  and sex = #{sex}
              </if>
          </where>
      </select>
    • <set>:有选择的进行更新处理,至少更新一列

      <update id="updateBySet" parameterType="users">
          update users
          <set>
              <if test="userName != null and userName != ''">
                  username = #{userName},
              </if>
               <if test="sex != null and sex != ''">
                  and sex = #{sex}
              </if>
          </set>
          where id = #{id}
      </update>
    • <foreach>:用来进行循环遍历,完成循环条件,批量删除(最常用),批量增加,批量更新。

      <select id="getById" resultType="users">
          select <include refid="allColumns"></include>
          from users
          where id in
              <foreach collection="array" item="id" separator="," open="("close=")">              #{id}
              </foreach>
      </select>
      ​
      <foreach>参数详解:
          collection:用来指定入参类型,如果是List集合,则为list;如果是Map集合,则为map;如果是数组,则为array。
          item:每次循环遍历出来的值或对象。
          separator:多个值或对象或语句之间的分隔符。
          open:整个循环外面的前括号。
          close:z
    • 批量删除实现:

      //int deleteBatch(Integet[] arr);
      <delete id="deleteBatch">
          delete from users
          where id in
          <foreach collection="array" item="id" separator="," open="(" close=")">     
              #{id}
          </foreach>
      </delete>
    • 批量增加(用的少)

      //int insertBatch(List<Users> list);
      insert into users(username,age,sex,address) values
      <foreach collection="list" item="u" separator=",">
          (#{u.userName},#{u.age},#{u.sex},#{u.address})    
      </foreach>
    • 批量更新

      • 注意:要使用批量更新,必须在jdbc.properties属性文件中的url中添加&allowMultiQueries=true.表示允许多行操作。(底层是分号结尾,即同时更新多条语句,但是一条语句里面有多行数据受影响,所以要添加该语句)

  • 指定参数位置

  • 入参是map(重点)

    • 如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更加明确。

      //入参是map
      List<User>getByMap(Map mpa);
      #{birthdayBegin}:就是map中得key
      ​
      <select id="getByMap" resultType="user">
          select <include refid="allColumns"></include>
          from user
          where birthday between #{birthdayBegin} and # {birthdayEnd}
      </select> 
          
      测试类中
          @Test
          public void testGetByMap() throws ParseException{
              Data begin = sf.parse("1999-01-01");
              Data end = sf.parse("1999-12-31");
          Map map = new HashMap<>();
          map.put("birthdayBegin",begin);
          map.put("birthdayEnd",end);
          List<User>list = uMapper.getByMap(map);
          list.forEach(user -> System.out.println(user));
          }


  • 返回值是map

    • 如果返回的数据实体类无法包含,可以使用map返回多张表中的若干数据。返回后这些数据之间没有任何关系,就是Object类型。

    • 返回的map的key就是列名或别名。

          //返回值是map(一行)
          //Map getReturnMap(Integer id);
          <select id="getReturnMap" parameterType="int" resultType="map">
              select username,address
              from user
              where id=#{id}
          </select>
      ​
      ​
          //返回多行的map
          //List<Map> getMap();
          <select id="getMap" resultType="map">
              select username,age
              from user
          </select>


  • 如果实体类的成员名称和表名中的名称不一致,查询时可以用别名解决。

  • 使用resultMap手工完成映射

    //private Integer id;
    //private String name;
    ​
    //使用resultMap手工完成映射
    //typed实体类
    <resultMap id="bookmap" type="book">
        //主键绑定
        <id property="id" column="bookid"></id>     -->property是主键的成员变量名,column是表中的主键名
        //非主键绑定
        <result property="name" column="bookname"></result> -->
    </resultMap>
    <select id="getAll" resultMap="bookmap">
        select bookid,bookname
        from book
    </select>
  • 表之间的关联关系

    • 一对多关联

    • 多对一关联

    • 一对一关联

    • 多对多关联

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。