Mybatis

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Mybatis,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

1.Mybatis
1.1 介绍
  • MyBatis 是一款优秀的持久层框架,它支持定制化SQL(灵活的修改)**、**存储过程(函数)以及高级映射(javabean和数据库对象的映射→输入映射和输出映射)。MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集。
    • Mybatis就是一个帮助我们去自定义SQL语句,然后对数据库中的数据进行增删改查的一个框架,这种框架我们统一叫做ORM框架
      • Object relationship mapping 对象关系映射
    • MyBatis 将sql语句暴露给开发者,由开发者去编写。其他的封装起来。修改非常方便。(增删改查都是开发者来编写)
    • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs映射成数据库中的记录。(ORM和持久化)
  • 官方解释:
    • MyBatis-Spring 会将 MyBatis 代码无缝地整合到 Spring 中。允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

    使用人话来解释描述,就是,它可以将数据库里的一条条数据封装成一个个的JavaBean对象,来达到数据持久化的目的。

    另外,还可以让开发者只需要书写sql语句即可,其余的不需要去管,非常方便省心。

1.2.在IDEA中新建Mybatis的Maven项目
1.2.1.Mybatis基础
  • 在Maven中导入Mybatis的jar包

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    
  • 在resource中新建一个Mybatis的配置文件(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>
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <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>
      <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
      </mappers>
    </configuration>
    
    
    <!-- 添加并修改后如下 -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--Mybatis的主配置文件,上面的dtd文件定义了这个xml文件中的标签的名字,出现的位置,顺序-->
    <!-- configuration其实就是这个配置文件的头,表示这个配置文件的开始 -->
    
    <configuration>
    
        <!--environments 表示数据源的环境,default表示正在使用用的数据源环境-->
        <environments default="dev">
            
            <!--id 表示环境的名字-->
            <environment id="dev">
                <!--事务管理器 表示数据库事务交给谁去管理 , Type=JDBC表示交给JDBC去管理-->
                <transactionManager type="JDBC"/>
                
                <!--数据源(连接池)-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    
                    <!-- 若数据库有乱码问题,那么可以修改下列语句为
                        <property name="url" value="jdbc:mysql://localhost:3306/数据库名称?
                        useUnicode=true&amp;characterEncoding=utf-8"/>
                    -->
                    
                    <property name="url" value="jdbc:mysql://localhost:3306/数据库名称"/>
                    <property name="username" value="root"/>
                    <property name="password" value="密码"/>
                </dataSource>
            </environment>
            
        </environments>
        
        <!--表示Mybatis需要把哪个Mapper加载进来,找到mapper.xml文件的位置-->
        <mappers>
            <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
            <mapper resource="自建的接口的全路径名.xml"/>
            <!-- 也可以直接加入package标签,把接口的路径名写入,包下的xml文件可以全部映射 -->
            <!-- <package name="包名"/> -->
        </mappers>
    </configuration>
    
  • 在对应的数据库中新建一个表

    CREATE TABLE user (
        `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
        `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `age` int(11) NULL DEFAULT NULL,
        PRIMARY KEY USING BTREE (`id`)
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
    
  • 在java文件夹中创建对应Javabean,成员变量要可以和表中的名称不相同(DBUtils中必须相同),但是数据类型要相同。

    • 这里暂时以相同的数据类型和字段名称为例
    public class User{
        private Integer id;
        private string username;
        private string username;
        private string username;,
        
        //省略所必备的get、set方法
    }
    
  • 在java文件夹中创建一个接口和一个xml文件

    • 接口,如UserMapper接口
      public interface UserMapper{}
      
    • xml文件,文件名必须和接口相同,这里应该为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">
      
      <!--
       1. 注意:命名空间必须是接口的全限定名称
       2. 这个配置文件在经过编译后,必须和我们的接口在同一个路径下
          为了实现这个目的,有两种手段
          2.1 在resources路径下,建立和我们的接口(如UserMapper)相同的包路径,然后把这个文件放进去
          2.2 在UserMapper这个接口文件下直接建立这个xml文件,但是必须要在pom.xml文件中加入以下配置
              <!--资源文件的配置-->
              <build>
                  <resources>
                      <resource>
                          <directory>src/main/java</directory>
                          <includes>
                              <!--&lt;!&ndash; 两个* 表示通配 *和我们的 _ 占位比较类似&ndash;&gt;-->
                              <include>**/*.xml</include>
                          </includes>
                      </resource>
                      <!-- 最好也要把mybatis.xml文件资源放进来,防止因build标签影响而产生bug -->
                      <reource>
                          <directory>src/main/resource</directory>
                          <includes>*.xml</includes>
                      </reource>
                  </resources>
              </build>
      -->
      
      <mapper namespace="新建的接口的全类名">
      	<!-- 
              <select>标签必须得有resultType,必须有返回值类型(返回标签是JavaBean的全类名)
      
      		如果没有resultType返回值,那么查询语句会报错,增删改语句返回影响的行数
              parameterType是基本类型和String类型的时候可以省略,如果通过Map、对象去传值,那么就不能省略
          -->
      
          <select id="selectAll" resultType="新建的封装的JavaBean的全类名">
              select * from user
          </select>
          
      </mapper>
      
  • 使用Mybatis查询所有数据

    @Test
    public void testSelectAll(){
    
        //第一步 创建sqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    
        // 第二步 获取输入流与sqlsession
        //        ClassLoader classLoader = MybatisTest.class.getClassLoader();
        //        InputStream inputStream = classLoader.getResourceAsStream("mybatis-config.xml");
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    
        // 第三步 获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
    
        // 第四步 执行sql
        // 这里就是接口的全类名+上面的select标签的id
        List<Object> list = sqlSession.selectList("新建的接口的全类名.selectAll");
    
        // 输出
        for (Object object : list) {
            System.out.println(object);
        }
        // 关闭资源
        sqlSession.close();
    }
    
  • 增删查改

    • 将公共部分封装
    	@BeforeClass
        public static void init(){
    
            //第一步 创建sqlSessionFactoryBuilder
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    
            // 第二步
    //        ClassLoader classLoader = MybatisTest.class.getClassLoader();
    //        InputStream inputStream = classLoader.getResourceAsStream("mybatis-config.xml");
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            } catch (IOException e) {
                e.printStackTrace();
            }
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    
            // 第三步 获取sqlSession
            sqlSession = sqlSessionFactory.openSession();
        }
    
        @AfterClass
        public static void destory(){
            sqlSession.close();
        }
    
    • 删除
    @Test
    public void testDelete(){
    
        /*
        	这里对应的语句要在UserMapper.xml中添加一对delete标签
        	id要一致
            <delete id="deleteUserById">
                delete from user where id = #{id}  这个取值的时候,#{} 里面的名字不作限制,但是不能省略,必须要有字符占位
            </delete>
        */
        // 这里的传递的字符串必须和配置文件中的namespace以及<insert> <update>标签的id值对应上,中间用.拼接
        int affectedRows = sqlSession.delete("新建的接口的全类名.deleteUserById", 1);
    	//因为xml里没有写返回类型,所以默认返回的是影响的行数
        System.out.println(affectedRows);
        // sqlSession默认不会提交,所以在做对数据库的数据进行更改、删除、增加的时候,需要手动提交,查询则不需要
        // 简单说就是需要改变数据库的值的时候,需要提交,只涉及查询则不需要
        sqlSession.commit();
    }
    
  • 删除

  @Test
  public void testUpdateUser(){
      /*
      	这里对应的语句要在UserMapper.xml中添加一对update标签
      	parameterType可以省略,id要一致
          <update id="updateGender" parameterType="string">
      		update user set gender = #{gender}
  		</update>
      */
      
      int affectedRows = sqlSession.update("新建的接口的全类名.updateGender", "male");
  	//因为xml里没有写返回类型,所以默认返回的是影响的行数
      System.out.println(affectedRows);
      sqlSession.commit();
  }
  • 增加(传入对象)
@Test
  public void testInsert(){
  	/* 
      	这里对应的语句要在UserMapper.xml中添加一对insert标签
  
          <insert id="insertUser" parameterType="新建的封装的JavaBean的全类名">
          	<!-- 要和成员变量的名字,即get方法名对应-->
      		insert into user values (#{id},#{username},#{password},#{gender})
  		</insert>
      */
      User user = new User();
      user.setId(9);
      user.setUsername("马云");
      user.setPassword("我这个人对钱没有兴趣");
      user.setGender("male");
      sqlSession.insert("新建的接口的全类名.insertUser",user);
  
      sqlSession.commit();
  }
  • 查询

    // 根据Id去查询
    @Test
    public void testSelectOneById(){
        /* 
        	这里对应的语句要在UserMapper.xml中添加一对select标签
            parameterType可以省略,id要一致
    
            <select id="selectOneById" resultType="新建的封装的JavaBean的全类名" parameterType="int">
                select * from user
            </select>
        */
        User user = sqlSession.selectOne("新建的接口的全类名.selectOneById", 5);
        System.out.println(user);
    }
    
    
    // 查询所有
    @Test
    public void testSelectAll2(){
        List<User> userList = sqlSession.selectList("新建的接口的全类名.selectAll");
        System.out.println(userList);
        //查询可以不用提交
    }
    
  • 修改(传入对象)

@Test
public void testUpdateByUser(){
    /* 
    	这里对应的语句要在UserMapper.xml中添加一对upadte标签

        <upadte id="updateUserById">
        	<!-- 要和成员变量的名字,即get方法名对应-->
    		update user set username=#{username},password=#{password},gender=#{gender} where id=#{id}
		</update>
    */
    User user = new User();
    user.setId(7);
    user.setUsername("猪八戒");
    user.setPassword("我回我的高老庄");
    user.setGender("male");
    sqlSession.insert("新建的接口的全类名.updateUserById",user);
    
    sqlSession.commit();
}
1.3.Mybatis动态代理
  • 当前的使用方式是在代码里面直接写sql语句的路径,这种方式其实是很不灵活的,使用起来也很不方便,Mybatis给我们提供了UserMapper接口可以帮助我们生成代理对象,我们通过直接调用接口中的方法,底层就是去调用代理对象里面生成的方法,通过这种方式来实现接口的调用
  • 使用方式
    1. 在UserMapper.xml文件中添加配置
      <!--resultType记录的是一行记录对应的类型,而不是所有的,如果查询到多个数据,会自动封装为List-->
      <select id="selectList" resultType="自建的JavaBean全类名">
          select * from user
      </select>
      
    2. 在UserMapper接口中声明方法
      //返回值类型要与查询语句相对应,方法名要与标签的id相对应
      List<User> selectList();
      
    3. 在测试类中使用
      @Test
      public void selectList(){
          UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
          List<User> users = userMapper.selectList();
          System.out.println(users);
      }
      
  • 对应关系补充:
    1. UserMapper.xml文件中的namespace必须和接口的全限定名称对应
    2. UserMapper.xml文件中的标签的id值必须和方法名对应
    3. UserMapper.xml文件中的参数类型(parameterType)和的方法的参数对应
    4. UserMapper.xml文件中的返回值类型(resultType)必须和的方法的返回值类型对应
  • 实现过程:Mybatis根据配置文件中的标签来确定这个方法是insert、update、select、delete中的哪一个方法,然后如果标签是update、delete、insert的话,那么就直接执行 sqlSession.update或sqlSession.delete或sqlSession.insert 这类方法,如果是标签是的话,就需要看方法的返回值类型是一个bean还是多个bean,然后再去执行对应的方法,对于查询到多个结果,那么返回值必须设置为List类型
  • Example
    • UserMapper:
      public interface UserMapper {
      
          User selectOneUserById(Integer id);
      
          List<User> selectListUser();
      
          User selectUserByName(String name);
      
          Integer insertUser(User user);
      
          Integer updateUserByMale(String male);
      
          Integer deleteUserById(Integer id);
      }
      
    • UserMapper.xml
      <mapper namespace="com.cskaoyan.dao.UserMapper">
      
          <select id="selectUserById" resultType="com.cskaoyan.user.User" parameterType="int" >
            select * from user where id = #{id}
          </select>
      
          <!--resultType记录的是一行记录对应的类型,而不是所有的-->
          <select id="selectListUser" resultType="com.cskaoyan.user.User">
              select * from user
          </select>
      
          <select id="selectUserByName" resultType="com.cskaoyan.user.User">
              select * from user where username = #{name}
          </select>
      
          <insert id="insertUser" parameterType="com.cskaoyan.user.User">
              insert into user values (#{id},#{username},#{password},#{gender})
          </insert>
      
          <update id="updateUserByMale" parameterType="string">
              update user set gender = 'female' where gender = #{gender}
          </update>
      
          <delete id="deleteUserById">
              delete from user where id = #{id}
          </delete>
      </mapper>
      
1.4.Mybatis配置
1.4.1.properties配置标签
  • 新建一个properties文件
    url=jdbc:mysql://localhost:3306/28_jdbc
    driverClassName=com.mysql.jdbc.Driver
    username=root
    password=123456
    
  • 在mybatis-config.xml文件中引入这个properties配置文件
    <configuration>
        <!-- 注意这个标签的位置应该在configuration标签内的最顶部 -->
    	<properties resource="jdbc.properties"/>
        <!-- ...... -->
    </configuration>
    
  • 在mybatis-config.xml中可以通过$来取值
    <datasource type="POOLED">
        <property name="driver" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </datasource>
    
1.4.2.setting日志标签
  • 在mybatis-config.xml中开启日志
    <properties resource="jdbc.properties"/>
    <!-- 注意这个标签的位置应该在properties标签下(如果有properties标签) -->
    <settings>
        <!--这个是一个日志的配置-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
1.4.3.TypeAlaises别名标签
  • 在mybatis-config.xml中给一些类起别名
    </settings>
    <!-- 注意这个标签的位置应该在settings标签下(如果有settings标签) -->
    <typeAliases>
        <typeAlias type="自建的JavaBean全类名" alias="user" /> <!-- alias就是JavaBean类的别名 -->
    </typeAliases>
    
  • 在UserMapper.xml文件中使用
    <!-- 在这里就可以替换为别名 -->
    <select id="selectUserByName" resultType="user">
    	select * from user where username = #{name}
    </select>
    
    <!-- 类似这种,Mybatis已经定义好了别名,就不需要自己再起别名了 -->
    <update id="updateUserByMale" parameterType="string">
    	update user set gender = 'female' where gender = #{gender}
    </ update>
    
1.4.4.Mapper映射
  • 将接口.xml文件加载进内存并参与编译,有两种方式
  • 在mybatis-config.xml中的mappers标签下配置
    <mappers>
        <!-- 方式一 -->
        <mapper resource="com/cskaoyan/dao/UserMapper.xml" />
        
        <!-- 方式二 -->
        <!-- 配置了这个包名以后,Mybatis会去把这个包里面的所有的配置文件加载进来 --> 
        <package name="UserMapper.xml文件所在包的路径名"/>
    </mappers>
    
1.5.输入映射
  • 在使用Mybatis的时候,传递参数的方式
1.5.1.传入一个简单的参数
  • UserMapper接口中
    // 传一个简单类型的参数,这个简单参数可以是基本类型和String
    User selectUserById(Integer id);
    
    // 这个是使用注解
    User selectUserByIdWithParam(@Param("userid") Integer id);
    
  • UserMapper.xml文件中
    <!--这里建议使用全限定名称,可读性更强-->
    <select id="selectUserById" resultType="com.cskaoyan.user.User">
        <!-- 这个#{}里的字符串可以书写任意值 -->
        select  * from user where id = #{id}
    </select>
    
    <select id="selectUserByIdWithParam" resultType="com.cskaoyan.user.User">
        <!-- 这个#{}里的字符串必须要和注解中的相同 -->
        select  * from user where id = #{userid}
    </select>
    

    注意:当我们传一个简单参数的时候

    • 假如在参数里面加了@Param注解的时候,那么在后面xml文件中取值的时候一定要使用注解里面配置的value值来取值,不然会报错
    • 假如没有在参数里面加@Param注解,那么后续在xml文件中去取值的时候,可以#{任意值} ,大括号里面可以是任意值
1.5.2.传入多个简单类型的参数
  • UserMapper接口中
    /**
         * 通过id和username去修改gender的值
         */
    Integer updateUserGenderByIdAndName(@Param("id") Integer id,
                                        @Param("username") String username,
                                        @Param("gender") String gender);
    
  • UserMapper.xml文件中
    <update id="updateUserGenderByIdAndName">
        update user set gender = #{gender} where id =#{id} and username =#{username}
    </update>
    

    注意:

    • 在使用多个简单类型的参数进行传值的时候,假如没有在接口方法的参数前面加上@Param注解,后续Mybatis在获取参数的时候,会找不到参数,会报 bindingException(绑定异常),所以需要加上@Param注解,这一点和传一个简单类型的参数不一样
1.5.3.传入一个POJO(JavaBean)
  • UserMapper接口中
    // 通过JavaBean去传值
    Integer insertUser(User user);
    
    // 使用注解
    Integer insertUserWithParam(@Param("user") User user);
    
  • UserMapper.xml文件中
    <insert id="insertUser" parameterType="com.cskaoyan.user.User">
        insert into user values (#{id},#{username},#{password},#{gender})
    </insert>
    
    <insert id="insertUserWithParam" parameterType="com.cskaoyan.user.User">
        insert into user values (#{user.id},#{user.username},#{user.password},#{user.gender})
    </insert>
    

    注意:在使用Java对象进行传值的时候,

    • 如果没有使用@Param注解,那么我们获取值的时候直接在#{成员变量名}。
    • 如果我们使用了@Param注解,如:Param(value= “user”),在xml文件里面取值的时候,应使用#{user.成员变量名}取值
1.5.4.传入一个Map
  • 一般传值的时候不用map来传值,如果使用Map来传值,那么对于Map中的键值对内容是不清楚的,并不能直接看到,对于开发者来说还行,但是对于代码的维护者来说十分不友好,所以一般不采用Map进行传值。如果有这种需求,可以把Map中的键值对给他封装到一个JavaBean中,然后采用对象来传值。
  • UserMapper接口中
    // 通过map来传值
    List<User> selectListByGenderAndId(Map<String,Object> map);
    
  • UserMapper.xml文件中
    <select id="selectListByGenderAndId" parameterType="map" resultType="com.cskaoyan.user.User">
        select * from user where id > #{id} and gender = #{gender}
    </select>
    

    添加了@Param之后,规则和JavaBean是一样的:使用Map去取值的时候,应使用 #{key} 方式

1.5.5. 通过位置传值
  • 位置对应的是接口的参数列表里面参数出现的位置,如果是第一个,可以用#{arg0}来取,也可用#{param1}来取
    • #{arg0},#{arg1}, #{arg2},,arg从索引0开始
    • #{param1}, #{param2},#{param2},param索引从1开始
  • UserMapper接口中
    // 通过位置来传值
    Integer updateUserGenderByIdAndNameByIndex(Integer id, String username, String gender);
    
  • UserMapper.xml文件中
    <!--第一种-->
    <update id="updateUserGenderByIdAndNameByIndex">
        update user set gender = #{param3} where id =#{param1} and username =#{param2}
    </update>
    
    <!--第二种-->
    <update id="updateUserGenderByIdAndNameByIndex">
        update user set gender = #{arg2} where id =#{arg0} and username =#{arg1}
    </update>
    

    通过位置传值容易出错,不推荐使用。

1.5.6.#和$符号的区别
  • $去取值的时候,是通过字符串拼接的方式去构建sql语句,底层是statement对象
  • #去取值的时候,是通过 ? 占位的方式去构建sql语句的,底层是prepareStatement对象
    • 因此使用#去取值更安全,而使用$符号有sql注入的风险
    通过#符号取值
    Setting autocommit to false on JDBC Connection _[com.mysql.jdbc.JDBC4Connection@30ee2816]
    ==>  Preparing: select *from user uhere id = ?
    ==> Parameters:2(Integer)
    <==		columns : id, username,password,gender
    <==		Row: 2, lanzhao520,changfeng123,female
    <==		Total: 1
    User{id=2,username='lanzhao520', password='changfeng123' , gender='female'}
    
    
    
    通过$符号取值
    Setting autocommit to false on JDBC Connection _[com.mysql.jdbc.JDBC4Connection@30ee2816]
    ==>  Preparing: select *from user uhere id = 2
    ==> Parameters:
    <==		columns : id, username,password,gender
    <==		Row: 2, lanzhao520,changfeng123,female
    <==		Total: 1
    User{id=2,username='lanzhao520', password='changfeng123' , gender='female'}
    
  • $的特殊用处
    • $符号,可以传递表名、列名,而#做不到
      • UserMapper接口中
        List<User> selectListByTableName(@Param("tableName") String tableName);
        
      • UserMapper.xml文件中
        <select id="selectListByTableName" resultType="com.cskaoyan.user.User">
            select * from ${tableName}
        </select>
        
    • 在使用order by 、group by 这样的关键字的时候,如果order by、group by后面的参数是通过外界传入的,则只能通过$ 来取值
      • order by可以通过#取到值,但是这个取到的值并不准确
      • select * from user order by ? desc limit 1,后面的desc没有生效,导致取到的结果是升序排序的第一行记录

        order by 后面不能通过 #{} 来取值,只能通过

        {} 来取值,通过

        符号来取值的话,会有sql注入的风险,这个风险来自于用户给你输入一些非法字符串,例如淘宝网查询商品排序的方式,排序的字段没有交给用户自己去输入,而是给用户提供了一些固定的排序的按钮,这些按钮后面就是对应着相应的字段,由于这个字段不是用户自己输入的,而是由程序员在开发程序的时候自己定义好的,所以在使用order by的时候才能避免这种sql注入的风险。

        • 实际开发中会用的$有分页,排序,like模糊查询等。(重点)
        • 实际开发中,若动态查询表名,列名,拼接的sql则必须用$,否则会解析异常。
1.6.输出映射
1.6.1. 简单对象
  • UserMapper接口中
    // 根据用户的id,获取用户的username
    String selectUsernameById(@Param("id") Integer id);
    
    // 查询一共有多少行记录
    Integer selectCount();
    
  • UserMapper.xml文件中
    <select id="selectUsernameById" resultType="java.lang.String">
        select username from user where id = #{id}
    </select>
    
    <select id="selectCount" resultType="int">
        select count(1) from user
    </select>
    
1.6.2.JavaBean
  • 输出对象是一个JavaBean的时候,resultType=“ ” ,引号内必须得配置这个输出映射对象的全限定名称,或者是这个全限定名称的别名。
  • 查询用户列表
    • UserMapper接口中
      List<User> selectUserList();
      
    • UserMapper.xml文件中
      <select id="selectUserList" resultType="com.cskaoyan.user.User">
          select * from user
      </select>
      
  • JavaBean中的变量名与sql语句中的字段名无法对应,可以通过起别名的方式来解决,as前是数据库中字段的名称,as后是封装到JavaBean的对应变量的名称
    • UserMapper接口中
      List<UserVO> selectUserVOList();
      
    • UserMapper.xml文件中
      <select id="selectUserVOList" resultType="com.cskaoyan.user.UserVO">
          select id as id ,username as name ,password as passwd, gender as gender from user
      </select>
      
1.6.3.ResultMap
  • UserMapper接口中
    List<UserVO> selectUserVOListUseResultMap();
    
  • UserMapper.xml文件中
    <!--ResultMap标签中的属性意义
        id: resultMap的名字,在其他的sql片段中使用的时候要使用这个id的值
        type:resultMap映射为目标对象
    
        <!--ResultMap标签下的标签用法意义
    
            id : 表里面的主键映射,后面详细叙述
                    property: JavaBean中的成员变量的名字
                    column: 数据库中的字段的列名
                    javaType: JavaBean中的变量的类型
                    jdbcType: 数据库表中的字段的类型
    
                    javaType和jdbcType要对应起来,这两个可以省略
    
            result: 如果是普通字段,就使用result标签就可以
        -->
    <resultMap id="userVOMap" type="com.cskaoyan.user.UserVO">
        <id property="id" column="id" javaType="java.lang.Integer" jdbcType="INT"/>
        <result property="name" column="username"/>
        <result property="passwd" column="password"/>
    </resultMap>
    
    <select id="selectUserVOListUseResultMap" resultMap="userVOMap">
        select * from user
    </select>
    
1.7.动态sql
1.7.1 where
  • 给sql语句里面拼装一个where关键字,如果where标签里面的if标签都没有满足条件的,那么不会拼接 where关键字

  • where标签会自动去除标签里面的第一个and 或者 or关键字

  • UserMapper接口中

    User selectOneByIdWhere(@Param("id") Integer id);
    
    List<User> selectListBySelective(User user);
    
  • UserMapper.xml文件中

    <select id="selectOneByIdWhere" resultType="com.cskaoyan.user.User">
        select * from user
        <where>
            id = #{id}
        </where>
    </select>
    
    <select id="selectListBySelective" resultType="com.cskaoyan.user.User">
        select * from user
        <where>
            <if test="id != null">
                and id = #{id}
            </if>
            <if test="username != null">
                or username = #{username}
            </if>
            <if test="password != null">
                and password = #{password}
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
        </where>
    
    </select>
    
1.7.2 if
  • if标签就是动态的去给我们的sql语句去做判断,根据传进来的值去做判断。
    <if test="required" >
    
  • test后面的内容就是需要附加一个OGNL表达式,这个OGNL表达式的结果是一个Boolean类型的值
    OGNL表达式 逻辑表达式
    == 等于
    != 不等于
    gt 大于
    lt 小于
    gte 大于等于
    lte 小于等于
  • xml文件中有些特殊字符也不能使用,如:< > & ,这些要使用转义字符
    转义前 转义后
    & &amp;
    > &gt;
    < &lt;
    >= &gt;=
    <= &lt;=
  • UserMapper接口中
    // 假如我们传进来的年龄大于20 那么就查询比20大或者等于20的员工,否则就查询比20小的员工
    List<Employee> selectList(User user);
    
  • UserMapper.xml文件中
    <!-- 假如我们传进来的年龄大于等于20 那么就查询比20大或者等于20的员工,否则就查询比20小的员工-->
    <select id="selectList" resultType="com.cskaoyan.user.User">
        select * from user
        <where>
            <!-- test属性相当于测试类里传入进来的参数的限制 -->
            <!-- 如,传入的参数大于等于20,那么就执行该条语句,如果小于20,那么就执行下面的语句 -->
            <if test="age gte 20">
                and age &gt;= 20
            </if>
            <if test="age lt 20">
                and age &lt; 20
            </if>
        </where>
    </select>
    

1.7.3 SQL片段
  • 把一些反复利用的sql片段抽离出来,形成一个sql片段,这个sql片段可以反复利用
  • UserMapper.xml文件中
    <sql id="base_column">
    	id,age,name,gender
    </sql>
    
    <select id="selectListSql" resultType="com.cskaoyan.user.User">
    
        <!-- sql片段中的id值要和这里的相同 -->
        select  <include refid="base_column"/> from user
        <where>
            <if test="age gte 20">
                and age &gt;= 20
            </if>
            <if test="age lt 20">
                and age &lt; 20
            </if>
        </where>
    </select>
    
1.7.4 trim
  • trim标签可以动态的在sql语句里面去增加或者是减少一些字符
  • UserMapper接口中
    // 根据条件修改用户
    Integer updateUserBySelective(User user);
    
  • UserMapper.xml文件中
    <!--
        trim 标签可以动态的帮助我们去增加或者删除sql里面的一些字段
        prefix: 指在trim包起来的sql语句的前面去增加指定的字符
        suffix: 指在trim包起来的sql语句的后面去增加指定的字符
        suffixOverrides: 指在trim包起来的sql语句的后面去除指定的字符
        prefixOverrides: 指在trim包起来的sql语句的前面去除指定的字符
    
                update employee set name = #{name}, age = #{age},gender = #{gender}
                where id = #{id}
        -->
    <update id="updateUserBySelective" parameterType="com.cskaoyan.user.User">
        update user
        <trim prefix="set" suffixOverrides=",">
            <if test="name != null">
                name = #{name},
            </if>
            <if test="age != null">
                age = #{age},
            </if>
            <if test="gender != null">
                gender = #{gender},
            </if>
        </trim>
        <where>
            id = #{id}
        </where>
    </update>
    
1.7.5 set
  • set标签和 trim prefix="set" suffixOverrides="," 等价
  • UserMapper.xml文件中
    <update id="updateUserBySelectiveBySET" parameterType="com.cskaoyan.user.User">
        update user
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="age != null">
                age = #{age},
            </if>
            <if test="gender != null">
                gender = #{gender},
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>
    
1.7.6 foreach
  • in语句
    • UserMapper接口中
    // foreach 根据id的list去查询用户 例如:select * from user where id in (1,2,3,4);
    List<Employee> selectEmployeeByIds(List<Integer> ids);
    
    // 指定了参数名称的,在xml文件中的collection属性的名称一定要一致
    List<Employee> selectEmployeeByIds(@Param("ids")List<Integer> ids);
    
    • UserMapper.xml文件中
    <!--
        foreach 表示去循环遍历一个集合
        collection:表示这个集合的名称,假如传进来的集合没有名称(没有加@Param注解),那么就是list,如果有名称,那么就使用指定的名称
        open:表示这个循环以什么开始
        close:表示这个循环以什么符号结束
        separator: 表示我们循环中的每个元素以什么符号分隔开
        index:表示每个元素的下标
        item:指代集合中的每个元素
    -->
    <select id="selectEmployeeByIds" parameterType="int" resultType="com.cskaoyan.user.user">
        select  <include refid="base_column"/>  from user
        <where>
            id in
            <!-- 未指定名称的,可以用list代替 -->
            <foreach collection="list" open="(" close=")" separator="," index="index" item="id">
                #{id}
            </foreach>
        </where>
    </select>
    
  • 批量插入
    • UserMapper接口
      // 批量插入
      Integer insertBatch(@Param("employeeList") List<User> userList);
      
    • UserMapper.xml文件
      <insert id="insertBatch" parameterType="com.cskaoyan.user.User">
          insert into user values
          <foreach collection="employeeList" separator="," item="user">
              (#{user.id},#{user.name},#{user.age},#{user.gender})
          </foreach>
      </insert>
      
1.7.7 choose when
  • 这两个标签就相当于Java中的 if{} else {}
  • UserMapper接口中
    // 假如我们传进来的年龄大于20 那么就查询比20大或者等于20的员工,否则就查询比20小的用户
    List<User> selectList(User user);
    
    // 上面是<if>标签
    List<User> selectListByAge(@Param("age") Integer age);
    
  • UserMapper.xml文件中
    <!-- 假如我们传进来的年龄大于等于20 那么就查询比20大或者等于20的员工,否则就查询比20小的用户 -->
    <select id="selectList" resultType="com.cskaoyan.user.User">
    
        select * from user
        <where>
            <if test="age gte 20">
                and age &gt;= 20
            </if>
            <if test="age lt 20">
                and age &lt; 20
            </if>
        </where>
    </select>
    
    <select id="selectListByAge" resultType="com.cskaoyan.user.User">
        select * from user
        <where>
            <choose>
                <when test="age gte 20">
                    and age &gt;= 20
                </when>
                <otherwise>
                    and age &lt; 20
                </otherwise>
            </choose>
        </where>
    </select>
    
1.7.8 selectKey
  • selectKey标签是可以在sql语句执行之前或者是之后额外的执行一条sql语句
  • 如果:数据库某个表主键是自增的,那么在插入数据的时候,若不指定这个主键(也就是说把主键交给数据库去维护) 那么在某些业务场景下,假如我们插入了一条数据,我们需要知道这条数据在表中的主键的话,那么就可以使用selectKey
  • UserMapper接口中
    // 插入一条员工记录
    Integer inserUserWithSelectKey(Employee employee);
    
    // 插入一条员工记录,带参数。
    Integer inserUserWithSelectKey2(@Param("employee") Employee employee);
    
  • UserMapper.xml文件中
    <insert id="inserUserWithSelectKey" parameterType="com.cskaoyan.user.Employee">
        <!-- 
    		keyproperty属性的值应该和JavaBean类中的成员变量名称相同 
    		keyColumn属性的值应该和数据库表中字段的值相同
    		order属性的值表示在执行sql语句之前还是之后(before或after)
    		resultType属性表示返回的值
    	-->
        <selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="java.lang.Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert  into employee values (null,#{name},#{age},#{gender})
    </insert>
    
    
    <insert id="inserUserWithSelectKey2" parameterType="com.cskaoyan.user.Employee">
        <selectKey keyProperty="employee.id" keyColumn="id" order="AFTER" resultType="java.lang.Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert  into employee values (null,#{employee.name},#{employee.age},#{employee.gender})
    </insert>
    
  • 测试类
    // 通过selectKey标签获取插入的主键的值
    @Test
    public void testInsertUserAndGetId(){
        User user = new User();
        user.setName("短风");
        user.setEmail("changfeng@163.com");
        user.setAge(18);
        Integer id = userDao.insertUserRespId(user);
    
        System.out.println(user.getId());
    }
    
  • 获取主键的另一种方式,useGeneratedKeys属性
    <!-- 获取mysql自动帮助我们生成的主键,true,会自动获取,false则不会 -->
    <insert useGeneratedKeys="true" ... />
    
  • UserMapper接口中
    Integer insertUserRespIdUseGeneratedKeys(User user);
    
    Integer insertUserRespIdUseGeneratedKeysWithParam(@Param("user") User user);
    
  • UserMapper.xml中
    <!--
    	useGeneratedKeys: 表示开启自增主键的功能 
    	keyProperty: 表示把值映射到哪里去
    -->
    <insert id="insertUserRespIdUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
        insert into user values (null,#{name},#{email},#{age})
    </insert>
    
    <!-- 如果函数中有参数注解,那么在keyProperty标签中,一定要配置好封装的目标 -->
    <insert id="insertUserRespIdUseGeneratedKeysWithParam" useGeneratedKeys="true" keyProperty="user.id">
        insert into user values (null,#{user.name},#{user.email},#{user.age})
    </insert>
    
  • 测试类
    @Test
    public void testInsertUserRespIdUseGeneratedKeys(){
        User user = new User();
        user.setName("李白龙");
        user.setEmail("libailong@163.com");
        user.setAge(38);
        Integer id = userDao.insertUserRespIdUseGeneratedKeys(user);
    
        System.out.println("获取到的用户id:" + user.getId());
    }
    
1.8 Mybatis插件
1.8.1 Lombok
  • 用来生成一个JavaBean里面的getter、setter、toString、equals方法的

  • 导包

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
    
  • 在IDEA中下载Lombok插件

  • Lombok插件的注解

    @Getter  // 生成Getter方法
    @Setter // 生成Setter方法
    @ToString // 生成toString 方法
    @NoArgsConstructor // 生成无参构造
    @AllArgsConstructor // 生成全参构造
    @Data  //生成getter、setter、toString、无参构造、equals、hashCode、canEqual方法
    
1.8.2 Mybatis的插件
  • 任意下载以下的其中之一
    • Free MyBatis plugin
    • MyBatisCodeHelperPro
    • MyBatisCodeHelperPro(Marketplace)
  • 功能:
    • 完成mapper文件与xml文件之间的快速跳转
    • 在xml文件里面生成 标签
    • 快速生成@Param注解
    • 写sql语句,甚至排错
    • 测试sql语句(收费功能)

    底层:其实就是用了Java agent 以及去做了一些idea的插件

1.9.多表查询
  • Mybatis的多表查询又称为嵌套查询,一般用于处理复杂结果集
1.9.1 一对一
  • 一对一模型:学生和学生详情、商品和商品详情、商品和库存

  • 以商品和库存为例,建表

    # 商品表
    create table products (
        id int primary key auto_increment,
        name varchar(255),
        price decimal(10,2)
    )
    
    # 库存表
    create table stock(
        id int primary key auto_increment,
        num int,
        product_id int
    )
    
  • 创建JavaBean类

    //商品
    @Data
    public class Products{
        Integer id;
        String name;
        BigDecimal price;
        Stock stock;
    }
    
    //库存
    @Data
    public class Stock{
        Integer id;
        Integer num;
        Integer productId;
    }
    
  • 分次查询

    • 新建一个ProductsMapper接口
      Products selectById(@Param("id") Integer id);
      
    • ProductsMapper.xml文件
      <!-- 分次查询 -->
      <!--
          第一次查询: select id,name,price from priducts where id = #{id}
          第二次查询: select id,num,product_id from stock where product_id = #{id}
      	
      	id:resultMap的id,对应sql语句的resultMap属性的值
      	type:查询到的最终结果的类型
      	association: 复杂类型的关联
      -->
      
      <resultMap id="productsMap" type="com.cskaoyan.user.Products">
          <!-- 该id表示唯一主键映射,id的唯一作用就是在嵌套的映射配置时判断数据是否相同 -->
          <!-- property表示要封装的JavaBean类中对应的成员变量的值 -->
          <id column="id" property="id"/>
          <result column="name" property="name"/>
          <result column="price" property="price"/>
          
          <!--
      		property:代表在目标类中的一个引用类型的名字,如product对象中的stock成员变量,
      		javaType:第二个查询语句的结果的封装目标对象
      		column:第一次查询传递给第二次查询的参数,是第一次查询里,或者说第一张表里,某一列的参数值
      		select:将column的值传递给该select语句
      	-->
          <association property="stock" javaType="com.cskaoyan.user.Stock" column="id" 
                       select="com.cskaoyan.dao.ProductsMapper.selectStockByProductId"/>
      </resultMap>
      
      <!--查询入口-->
      <select id="selectById" resultMap="productsMap">
          select id,name,price from products where id = #{id}
      </select>
      
      <select id="selectStockByProductId" resultType="com.cskaoyan.user.Stock">
          select id,num,product_id as productId from stock where product_id = #{id}
      </select>
      
    • 注意检查一下resources里的mybatis的xml配置文件,是否将接口的xml文件成功配置加载进内存
  • 连接查询

    • ProductsMapper接口中
    Products selectByIdUseCrossQuery(@Param("id") Integer id);
    
    • ProductsMapper.xml文件中
    <!--  连接查询 -->
    <resultMap id="productCrossMap" type="com.cskaoyan.user.Products">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="price" property="price"/>
        <association property="stock" javaType="com.cskaoyan.user.Stock">
            <result column="sid" property="id"/>
            <result column="num" property="num"/>
            <result column="productId" property="productId"/>
        </association>
    </resultMap>
    <select id="selectByIdUseCrossQuery" resultMap="productCrossMap">
        SELECT
            p.id as id,
            p.name as name,
            p.price as price,
            s.id as sid,
            s.num as num,
            s.product_id as productId
        FROM
            products AS p,
            stock AS s
        WHERE
            p.id = s.product_id
            AND p.id = #{id}
    </select>
    

    注:如果并非这种一对一的嵌套引用类型的形式,例如,只是一个普通的JavaBean类型,成员变量只包含基本数据类型或其包装类型,或String,只不过里面的数据需要查询多张表,那么可以直接查询所有字段,封装成一个JavaBean对象即可,不需要使用association标签

1.9.2 一对多
  • 模型:班级和学生、省份和城市
  • 采用班级和学生模型
    # 创建班级表
    create table clazz (
        id int primary key auto_increment,
        name varchar(20)
    )
    
    
    # 创建学生表
    create table stu(
        id int primary key auto_increment,
        name varchar(20),
        age int,
        gender varchar(20),
        clazz_id int 
    )
    
  • 分次查询
    • ClazzMapper
    // 一对多分次查询
    Clazz selectClazzByIdWithStudents(@Param("id") Integer id);
    
    • xml
    <resultMap id="clazzCrossMap" type="com.cskaoyan.user.Clazz">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
    
        <!-- 
            分次查询使用说明:
            1. 需要使用collection标签
            2. 一对一的时候使用的是javaType,这里要使用ofType
    	-->
        <collection property="stuList" ofType="com.cskaoyan.user.Stu" column="id" 		
                    select="com.cskaoyan.dao.ClazzMapper.selectStuByClazzId"/>
    </resultMap>
    
    <select id="selectClazzByIdWithStudents" resultMap="clazzCrossMap">
        SELECT
            id,
            name
        from 
        	clazz where id = #{id}
    </select>
    
    <select id="selectStuByClazzId" resultType="com.cskaoyan.user.Stu">
        SELECT
        	id,name,age,gender,clazz_id as classId
        from 
        	stu where clazz_id = #{id}
    </select>
    
  • 连接查询
    • ClazMmapper
    // 一对多连接查询
    Clazz selectClazzByIdWithStudentsLeftJoin(@Param("id") Integer id);
    
    • xml
    <resultMap id="leftjoinMap" type="com.cskaoyan.user.Clazz">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <collection property="stuList" ofType="com.cskaoyan.user.Stu">
            <result column="sid" property="id"/>
            <result column="sname" property="name"/>
            <result column="age" property="age"/>
            <result column="gender" property="gender"/>
            <result column="classId" property="classId"/>
        </collection>
    </resultMap>
    
    <select id="selectClazzByIdWithStudentsLeftJoin" resultMap="leftjoinMap">
        SELECT
            c.id as id,
            c.name as name,
            s.id as sid,
            s.name as sname,
            s.age as age,
            s.gender as gender,
            s.clazz_id as classId
        FROM
            clazz AS c LEFT OUTER JOIN stu AS s ON c.id = s.clazz_id
        WHERE
            c.id = #{id}
    </select>
    
1.9.3 多对多
  • 和一对多相似
  • 多对多的模型:学生和课程
  • 分次查询
    • StuMapper接口
    // 根据学生id 查询学生信息以及他的选课信息
    Stu selectStudentByIdWithCourses(@Param("id") Integer id);
    
    • StuMapper.xml文件
    <!-- 多对多分次查询start-->
    <resultMap id="stuMap" type="com.cskaoyan.user.Stu">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="gender" property="gender"/>
        <result column="classId" property="classId"/>
        <collection property="courseList" ofType="com.cskaoyan.user.Course" 		
                    select="com.cskaoyan.dao.StuMapper.selectCourseByStuId" column="id"/>
    </resultMap>
    
    <select id="selectStudentByIdWithCourses" resultMap="stuMap">
        select
        id,
        name,
        age,
        gender,
        clazz_id as classId
        from stu where id = #{id}
    </select>
    
    
    <select id="selectCourseByStuId" resultType="com.cskaoyan.user.Course">
        SELECT
        c.id AS id,
        c.NAME AS name
        FROM
        s_t AS st
        LEFT JOIN course AS c ON c.id = st.c_id
        WHERE
        st.s_id = #{id}</select>
    
    <!-- 多对多分次查询 end-->
    
  • 连接查询
    • 连接查询其实就是一条sql语句解决问题
    -- 多对多连接查询
    -- 要通过学生的id去查询出这个学生的信息以及他的选课信息
    SELECT
    	s.id as id,
    	s.name as name,
    	s.age as age,
    	s.gender as gender,
    	s.clazz_id as classId,
    	c.id as cid,
    	c.name as cname
    FROM
    	stu AS s
    	LEFT JOIN s_t AS st ON s.id = st.s_id
    	LEFT JOIN course AS c ON st.c_id = c.id 
    WHERE
    	s.id = 1;
    
    • StuMapper接口
    // 多对多连接查询
    Stu selectStudentByIdCrossJoinWithCourses(@Param("id") Integer id);
    
    • StuMapper.xml文件
    <!-- 多对多连接查询 start-->
    <resultMap id="stuCrossMap" type="com.cskaoyan.user.Stu">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="gender" property="gender"/>
        <result column="classId" property="classId"/>
        <collection property="courseList" ofType="com.cskaoyan.user.Course">
            <result column="cid" property="id"/>
            <result column="cname" property="name"/>
        </collection>
    </resultMap>
    <select id="selectStudentByIdCrossJoinWithCourses" resultMap="stuCrossMap">
        SELECT
            s.id as id,
            s.name as name,
            s.age as age,
            s.gender as gender,
            s.clazz_id as classId,
            c.id as cid,
            c.name as cname
        FROM
            stu AS s
                LEFT JOIN s_t AS st ON s.id = st.s_id
                LEFT JOIN course AS c ON st.c_id = c.id
        WHERE
        	s.id = #{id}
    </select>
    <!-- 多对多连接查询 end-->
    
1.9.4.多表查询中的主键映射
  • 对于多表查询中的id标签,其唯一作用就是在嵌套的映射配置时判断数据是否相同,当配置id标签时,MyBatis只需要逐条比较所有数据中id标签字段值是否相同即可,可以提高处理效率。
    1. 对于id标签可以配置多个,例如联合主键时
    2. 很可能也会出现没有配置id的情况,这时MyBatis就会把resultMap中所有字段进行比较,如果所有字段的值都相同就合并,只要有一个字段值不同,就不合并。这时,当结果集字段数为M,记录数N,最少M×N次比较,相比配置id时的N次比较,效率相差更多,所以要尽可能配置id标签。
    3. 在嵌套结果配置id属性时,如果查询语句中没有查询id属性配置的列,就会导致id对应的值为null。这种情况下,所有值的id都相同,因此会使嵌套的集合中只有一条数据。所以在配置id列时,查询语句中必须包含该列。(对于这个说法,作者存疑,因为在查询语句中未查询id属性的列,会导致报错,有的时候也会默认一列值为主键,以后再测试)
1.9.5.Mybatis的实现原理
  1. 首先运行Dao接口中对应的xml文件中的sql语句
  2. 如果是update、delete、insert语句,那么会直接执行完毕,而对于select语句,会获取到结果集
  3. 通过resultType的返回类型,通过property属性的封装类型,反射到对应名称的set方法,根据结果集的信息对返回的结果集在内存中进行组装、赋值,构造
  4. 对于一对多的关系,mybatis会根据主键cloumn列的值去重复后再进行封装。
1.9.5.复杂结果集的封装
  • 概念:除基本数据类型及其包装类,以及String类型外的其他类型,如自建的引用类型,List类型,都称为复杂结果集
  • 例:对于省份的查询,省份里包含一些省份信息和所有城市的列表
    • 方式一:较为复杂
      • 根据mybatis的查询原理,修改其对应名称的set方法,使其变量名封装到指定的复杂类型内。例如对于省份和城市的查询
      //省份的信息
      public static class Province{
              
          private Integer id;
          private String name;
          private Data data;//表示省份的其他信息
          private List<City> cityList;//一个省份下辖很多城市
      
          private Integer dataId;//表示Data的id,因为重复字段问题要改名
      
          //省略其他所必须的get、set方法
          //Data是复杂类型,要对set的相关变量的方法进行修改以及添加新的set方法,而List结果集可以直接封装
          public void setDataId(Integer dataId){
              this.data.setId(dataId);
          }
          public void setGovernor(String governor){
              this.data.setGovernor(governor);
          }
          public void setClimate(String climate){
              this.data.setClimate(climate);
          }
          public void setProvinceId(Integer provinceId){
              this.data.setProvinceId(provinceId);
          }
          
          //如果其他代码不会用到这些类,可以使用内部类来封装
          private class Data{
              
              private Integer id;
              private String governor; //省长
              private String climate;  //气候
              private Integer provinceId;
      
              //省略所必须的get、set方法
          }
          
          private static class City{
              
              private Integer id;
              private String name;
              private Integer provinceId;
      
              //省略所必须的get、set方法
          }
      }
      
      
      
      //Dao接口中的方法
      Province selectProvince(Integer id);
      
      //xml文件中的查询语句
      <resultMap id="selectProvinceById" type="testSelect.province.Province">
          <id column="pid" property="id"/>
          <result column="pname" property="name"/>
          <result column="did" property="dataId"/>
          <result column="governor" property="governor"/>
          <result column="climate" property="climate"/>
          <result column="dataProvinceId" property="provinceId"/>
          <collection property="cityList" ofType="testSelect.province.Province$City">
              <result column="cid" property="id"/>
              <result column="cname" property="name"/>
              <result column="cityProvinceId" property="provinceId"/>
          </collection>
      </resultMap>
          
      <select id="selectProvince" resultMap="selectProvinceById">
          select
              p.id as pid,
              p.name as pname,
              d.id as did,
              d.gonvernor as gonvernor,
              d.climate as climate,
              d.provinceId as dataProvinceId,
              c.id as cid,
              c.name as cname,
              c.provinceId as cityProvinceId
          from
              provinces as p
                  left outer join cities as c
                  left outer join datas as d
                  	on p.id=c.provinceId and p.id=d.provinceId
        where
          	p.id = #{id}
      </select>
      
  • 方式二:相对简便
    • get、set方法不必改动,通过多表查询的association和collection标签封装为目标结果
    • 因为mybatis会自动封装为目标对象,所以不必在意重复字段问题
    //xml文件中的查询语句
    <resultMap id="selectProvinceById" type="testSelect.province.Province">
        
        <id column="pid" property="id"/>
        <result column="pname" property="name"/>
    
        <association property="data" javaType="testSelect.province.Province$Data">
            <result column="did" property="id"/>
            <result column="governor" property="governor"/>
            <result column="climate" property="climate"/>
            <result column="dataProvinceId" property="provinceId"/>
        </association>
    
        <collection property="cityList" ofType="testSelect.province.Province$City">
            <result column="cid" property="id"/>
            <result column="cname" property="name"/>
            <result column="cityProvinceId" property="provinceId"/>
            <result column="unitPrice" property="unitPrice"/>
        </collection>
    
    </resultMap>
    
    <select id="selectProvince" resultMap="selectProvinceById">
        select
            p.id as pid,
            p.name as pname,
            d.id as did,
            d.gonvernor as gonvernor,
            d.climate as climate,
            d.provinceId as dataProvinceId,
            c.id as cid,
            c.name as cname,
            c.provinceId as cityProvinceId
        from
            provinces as p
            	left outer join cities as c
            	left outer join datas as d
            		on p.id=c.provinceId and p.id=d.provinceId
      where
        	p.id = #{id}
    </select>
    
1.10.懒加载
  • 懒加载是指使用多表查询的分次查询的时候,可以不立即执行第二个sql语句,而是在我们需要用到里面的成员变量的时候再去执行。
  • 懒加载其实是优化性能的
  • 开启方式
    • 在mybatis-config.xml配置文件中
    <!-- 懒加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    
    • 在接口.xml文件中,连接查询的association标签后边添加fetchType属性,属性值为lazy,lazy相反的属性是eager。
    • 如:
      <association property="stock" javaType="com.cskaoyan.user.Stock" column="id"
                   select="com.cskaoyan.dao.ProductsMapper.selectstockByProductd" fetchType="lazy"/>
      
  • 当需要用到products里面的stock的时候,才会去执行第二条sql语句,如果不用products里面的stock,那么就不会加载第二条sql语句
1.11.缓存
  • 本质上是将数据放入缓存中,因为缓存中读取数据的速度远比硬盘中快得多
1.11.1.一级缓存
  • 一级缓存其实是sqlSession级别的。也就是如果我们使用同一个sqlSession去多次查询同一个sql语句,并且传入的条件是一样的,那么只有第一次会去查询数据库,后面的请求都会去我们的缓存空间里面去查。
// 测试一级缓存
@Test
public void testSelectStuById(){

    SqlSession sqlSession = MybatisUtils.getSqlSession();

    StuMapper mapper1 = sqlSession.getMapper(StuMapper.class);
    StuMapper mapper2 = sqlSession.getMapper(StuMapper.class);
    StuMapper mapper3 = sqlSession.getMapper(StuMapper.class);
    StuMapper mapper4 = sqlSession.getMapper(StuMapper.class);

    Stu stu1 = mapper1.selectStuById(1); //从数据库查
    sqlSession.commit();										// 提交了之后会使一级缓存失效
    Stu stu2 = mapper2.selectStuById(1); // 从缓存中查

    Stu stu3 = mapper3.selectStuById(1); // 从缓存中查
    Stu stu4 = mapper4.selectStuById(1); // 从缓存中查

    System.out.println(stu1);
    System.out.println(stu2);
    System.out.println(stu3);
    System.out.println(stu4);

}

// 测试一级缓存
@Test
public void testSelectStuById2(){

    SqlSession sqlSession = MybatisUtils.getSqlSession();

    StuMapper mapper1 = sqlSession.getMapper(StuMapper.class);

    Stu stu1 = mapper1.selectStuById(1); //从数据库查
    Stu stu2 = mapper1.selectStuById(1); // 从缓存中查
    Stu stu3 = mapper1.selectStuById(1); // 从缓存中查
    Stu stu4 = mapper1.selectStuById(1); // 从缓存中查

    System.out.println(stu1);
    System.out.println(stu2);
    System.out.println(stu3);
    System.out.println(stu4);

}

// 测试一级缓存
@Test
public void testSelectStuById3(){

    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    SqlSession sqlSession3 = MybatisUtils.getSqlSession();
    SqlSession sqlSession4 = MybatisUtils.getSqlSession();

    StuMapper mapper1 = sqlSession1.getMapper(StuMapper.class);
    StuMapper mapper2 = sqlSession2.getMapper(StuMapper.class);
    StuMapper mapper3 = sqlSession3.getMapper(StuMapper.class);
    StuMapper mapper4 = sqlSession4.getMapper(StuMapper.class);

    Stu stu1 = mapper1.selectStuById(1); //从数据库查
    Stu stu2 = mapper2.selectStuById(1); // 从数据库查
    Stu stu3 = mapper3.selectStuById(1); // 从数据库查
    Stu stu4 = mapper4.selectStuById(1); // 从数据库查

    System.out.println(stu1);
    System.out.println(stu2);
    System.out.println(stu3);
    System.out.println(stu4);

}
  • 总结:
   /**
    * 结论:Mybatis的一级缓存默认是开启的,
    *	一级缓存的生效时间为:
    *   	1. sql语句要是同一个
    *       2. 参数列表以及值保持一致
    *       3. 我们使用的mapper要取自同一个sqlSession,假如是不同的sqlSession的话,那么就取不到一级缓存
    *
	*	失效时间为:
    *       当sqlSession提交或者关闭的时候,就失效了
    *
    */
1.11.2.二级缓存
  • 二级缓存是Namespace级别的。
  • 开启方式
    • 第一步:要去Mybatis-config.xml文件中开启二级缓存
      <!-- 二级缓存总开关-->
      <setting name="cacheEnabled" value="true"/>
      
    • 第二步:我们需要把查询的对象进行序列化,也就是实现序列化接口,自己定义的接口要implements Serializable
    • 第三步:需要在我们需要配置的namespace,对应的mapper.xml文件中加入一个标签,,在mapper标签下,resultMap标签上。
  • 测试:
@Test
public void testLevel2Cache(){

    SqlSessionFactory factory = MybatisUtils.getFactory();

    SqlSession sqlSession1 = factory.openSession();
    SqlSession sqlSession2 = factory.openSession();
    SqlSession sqlSession3 = factory.openSession();
    SqlSession sqlSession4 = factory.openSession();
    SqlSession sqlSession5 = factory.openSession();


    StuMapper mapper1 = sqlSession1.getMapper(StuMapper.class);
    StuMapper mapper2 = sqlSession2.getMapper(StuMapper.class);
    StuMapper mapper3 = sqlSession3.getMapper(StuMapper.class);
    StuMapper mapper4 = sqlSession4.getMapper(StuMapper.class);
    StuMapper mapper5 = sqlSession5.getMapper(StuMapper.class);

    Stu stu1 = mapper1.selectStuById(1); //从数据库查

    // 存入缓存 这一步不能忘了
    sqlSession1.commit();
    sqlSession1.close();


    Stu stu2 = mapper2.selectStuById(1); // 从数据库查
    Stu stu3 = mapper3.selectStuById(1); // 从数据库查
    Stu stu4 = mapper4.selectStuById(1); // 从数据库查
    Stu stu5 = mapper5.selectStuById(1); // 从数据库查

    System.out.println(stu1);
    System.out.println(stu2);
    System.out.println(stu3);
    System.out.println(stu4);
    System.out.println(stu5);

}
1.12.特殊注解
  • @Alias:生成别名
  • 使用注解实现增删改查以及嵌套查询

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181047.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!