狂神Mybatis入门

Mybatis

环境

  • JDBC
  • java基础
  • JDK 1.8
  • Mysql5.7
  • maven 3.6.1
  • 开发工具 idea
  • Junit

SSM框架:配置文件的最好方式:看官网文档

1 简介

1.1什么是mybatis?

  • MyBatis是一款优秀的持久层框架
  • 支持定制SQL、存储过程以及高级映射
  • Mybatis避免了几乎所有的JDBC代码和手动设置参树,以及获取结果集
  • Mybatis可以使用简单的XML注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录
  • MyBatis本是apache的一个开源项目iBatis
  • 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis
  • 2013年11月迁移到Github

如何获得Mybatis?

  • Maven仓库:

    下载地址

      <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->  <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis</artifactId>      <version>3.5.2</version>  </dependency>
    
  • Github:Mybatis地址

  • 中文文档地址

1.2 持久化

数据持久化

  • 持久化就是将程序的数据保存在硬盘中
  • 内存特性:断电会失去信息
  • 数据库(jdbc),io文件持久化
  • 生活中的持久化:冷藏,写……

为什么需要持久化?

  • 我们需要将一些数据保存下来,方便以后使用
  • 内存价格过高(制造成本高)

1.3 持久层

复习学习过的层:Dao,Service,Controller

持久层的工作

  • 完成持久化工作的代码块
  • 层之间的接线非常明显

1.4 为什么需要Mybatis?

  • 帮助程序员将数据存入到数据库中
  • 传统的JDBC代码太复杂了
  • 简化、框架、自动化
  • 不用Mybatis也能实现,Mybatis可以使程序员在不知道底层原理的情况下,完成网站后台搭建
  • 优点:
    • 简单易学
    • 灵活
    • 解除sql与程序代码的耦合
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

2 第一个MyBatis程序

思路:搭建环境—>导入MyBatis—>编写代码—>测试

2.1 搭建环境

  • 搭建数据库

       -- 创建数据库   create database `mybatis`;   use mybatis;   -- 创建表   create table `user`(      `id` int(20) not null,      `name` varchar(30) default null,      `pwd` varchar(30) default null,      primary key(`id`)    )engine=InnoDB default charset=utf8mb4;   -- 插入数据   insert into `user`(`id`,`name`,`pwd`) values  (1,'千树','123'),  (2,'张三','123'),  (3,'李飞','123');
    
  • mybatis官方文档:文档地址

  • 新建普通maven项目作为父项目,导入sql驱动,mybatis,junit组件

      <!--导入依赖-->  <dependencies>   
          <!--mysql驱动-->   
          <dependency>   
              <groupId>mysql</groupId> 
              <artifactId>mysql-connector-java</artifactId>                                   <version>5.1.46</version>    
          </dependency>   
          <!--Mybatis-->  
          <dependency>      
              <groupId>org.mybatis</groupId>                                                 <artifactId>mybatis</artifactId>       
              <version>3.5.2</version>   
          </dependency>  
          <!--junit-->     
          <dependency>      
              <groupId>junit</groupId>  
              <artifactId>junit</artifactId>  
              <version>4.12</version>    
          </dependency> 
    </dependencies>
    
  • 新建新组件作为子级项目,普通maven的module

  • 添加配置文件:

    在src->main->resources目录下新建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}"/>//数据库名字,默认root                         <property name="password" value="${password}"/>//数据库密码,自己的数据库密码,一般为root        </dataSource>   
              </environment> 
          </environments> 
    </configuration>
    

    配置文件的作用就是连接数据库

2.2 创建模块

  • 编写mybatis工具类

      //SqlSessionFactory --生产--> SqlSession  public class MybatisUtils {      private static SqlSessionFactory sqlSessionFactory; //提升作用域      //获取工厂,固定代码      static {          try {              String resource="mybatis-config.xml";              InputStream inputStream = Resources.getResourceAsStream(resource);              sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);          } catch (IOException e) {              e.printStackTrace();          }      }      //获取sqlSession      //SqlSession完全包含了面向对象数据库执行SQL命令所需的方法      public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession();}  }
    

2.3 编写代码

  • 实体类

      public class User {      private int id;      private String name;      private String pwd;      public User() { }      public User(int id, String name, String pwd) {          this.id = id;          this.name = name;          this.pwd = pwd;      }      public int getId() {          return id;      }      public void setId(int id) {          this.id = id;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getPwd() {          return pwd;      }      public void setPwd(String pwd) {          this.pwd = pwd;      }      @Override      public String toString() {          return "User{" +                  "id=" + id +                  ", name='" + name + '\'' +                  ", pwd='" + pwd + '\'' +                  '}';      }  }
    
  • Dao接口

      public interface UserDao {      List<User> getUserList();  }
    
  • 接口实现类改为以xxxMapper.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">  <!--namespace:命名空间,绑定mapper/Dao接口-->  <mapper namespace="com.qian.dao.UserDao">  <!--id:接口的方法,resultType:接口的返回值类型-->      <select id="getUserList" resultType="com.qian.pojo.User">          select * from mybatis.user where id = #{id}      </select>  </mapper>
    

2.4 测试

  • 测试类

      public class UserDaoTest {      @Test      public void test(){          //获取SqlSession对象          SqlSession sqlSession = MybatisUtils.getSqlSession();          //获取mapper          UserDao mapper = sqlSession.getMapper(UserDao.class);          List<User> list = mapper.getUserList();          for (User u:list){              System.out.println(u);          }          //不推荐使用  /*      这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。  */  //        List<User> list = sqlSession.selectList("com.qian.dao.UserDao.getUserList");  //        for (User user : list) {  //            System.out.println(user);  //        }          //关闭SqlSession          sqlSession.close();      }  }
    

    异常1:org.apache.ibatis.binding.BindingException: Type interface com.qian.dao.UserDao is not known to the MapperRegistry.

    解决方法:每一个Mapper.xml文件都需要在src->main->resources目录下新建mybatis-config.xml的核心配置文件中注册

    <mappers>  <mapper resource="com/qian/dao/UserMapper.xml"></mappers>
    

    异常2:
    Error building SqlSession.
    The error may exist in com/qian/dao/UserMapper.xml
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
    Cause: java.io.IOException: Could not find resource com/qian/dao/UserMapper.xml

    解决方法:

    <!-- 在maven中,约定大于配置,在pom中添加此文件可以解决 --><build>  <resources>      <resource>          <directory>src/main/resources</directory>          <includes>              <include>**/*.properties</include>              <include>**/*.xml</include>          </includes>          <filtering>true</filtering>      </resource>      <resource>          <directory>src/main/java</directory>          <includes>              <include>**/*.properties</include>              <include>**/*.xml</include>          </includes>          <filtering>true</filtering>      </resource>  </resources></build>
    

    异常3:
    Error building SqlSession.
    The error may exist in com/qian/dao/UserMapper.xml
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
    Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 6;

    解决方法:

    <?xml version="1.0" encoding="UTF-8" ?>    <!-- 把mybatis-config.xml与mybatis-config.xml文件的encoding修改成下面的 --><?xml version="1.0" encoding="UTF8" ?>
    

    另一种解决方法:删除掉xxxMapper.xml文件中所有的中文注释

    异常4:
    Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

    解决方法:
    useSSL=true改为false(true也可以,需要在mysql中启用SSL)

    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=GMT%2B8"/>
    

2.5 总结

img

public class UserDaoTest {    @Test    public void test(){        //获取SqlSession对象        SqlSession sqlSession = MybatisUtils.getSqlSession();        try{            //获取mapper            UserDao mapper = sqlSession.getMapper(UserDao.class);            List<User> list = mapper.getUserList();            for (User u:list){                System.out.println(u);            }            //不推荐使用//        List<User> list = sqlSession.selectList("com.qian.dao.UserDao.getUserList");//        for (User user : list) {//            System.out.println(user);//        }        }finally {            //关闭SqlSession            sqlSession.close();        }    }}

3 增删改查实现

3.1 Mapper接口

public interface UserMapper {    //查询全部用户    List<User> getUserList();    //根据id查询用户    User getUserById(int id);    //增加新的用户    boolean insertNewUser(User u);    //删除用户    boolean deleteUserById(int id);    boolean deleteUserByName(String name);    //修改用户    boolean updateUserById(User u);}

3.2 xxxMapper.xml文件

<?xml version="1.0" encoding="utf8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:命名空间,绑定mapper/Dao接口--><!--id:接口的方法,resultType:接口的返回值类型--><mapper namespace="com.qian.dao.UserMapper">    <select id="getUserList" resultType="com.qian.pojo.User">        select * from mybatis.user    </select>    <select id="getUserById" parameterType="int" resultType="com.qian.pojo.User">        select * from mybatis.user where id=#{id}    </select>    <!-- 对象中的属性,可以直接取出来用 -->    <insert id="insertNewUser" parameterType="com.qian.pojo.User">        insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})    </insert>    <delete id="deleteUserById" parameterType="int">        delete from mybatis.user where id=#{id}    </delete>    <delete id="deleteUserByName" parameterType="String">        delete from mybatis.user where name=#{name}    </delete>    <update id="updateUserById" parameterType="com.qian.pojo.User">        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}    </update></mapper>

3.3 Test类

注意:增删改要提交事务!!!

public class UserDaoTest {    @Test    public void test(){        //获取SqlSession对象        SqlSession sqlSession = MybatisUtils.getSqlSession();        try{            //获取mapper            UserMapper mapper = sqlSession.getMapper(UserMapper.class);//            查询全表//            List<User> list = mapper.getUserList();//            for (User u:list){//                System.out.println(u);//            }            //根据id查询//            User user = mapper.getUserById(1);//            System.out.println(user);            //插入新用户,注意:更新,插入,删除都需要提交事务//            User user1 = new User(4,"李四","25615");//            boolean isInserted = mapper.insertNewUser(user1);//            sqlSession.commit();            //代码优化//            if (mapper.insertNewUser(new User(4,"李四","25615"))) sqlSession.commit();            //删除用户//            if (mapper.deleteUserById(4))sqlSession.commit();             if (mapper.deleteUserByName("李四"))sqlSession.commit();             //修改用户            if (mapper.updateUserById(new User(4,"王五","6849816")))sqlSession.commit();        }finally {            //关闭SqlSession            sqlSession.close();        }    }}

3.4 常见错误

  • 标签要匹配
  • resource绑定Mapper,需要使用路径
  • 配置文件中不要写注释
  • useSSL=true有时候无法使用,改为false

3.5 万能Map

  • UserMapper.java

      User getUser(Map<String,Object> map);  boolean addUser(Map<String,Object> map);
    
  • UserMapper.xml

      <select id="getUser" parameterType="map" resultType="com.qian.pojo.User">   
          select * from mybatis.user where id=#{userId} 
    </select> 
    <insert id="addUser" parameterType="map">  
        insert into mybatis.user (id, name, pwd) VALUES (#{userId},#{userName},#{password})  </insert>
    
  • Test.java

      @Test  public void test(){      //获取SqlSession对象   
          SqlSession sqlSession = MybatisUtils.getSqlSession();   
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);   
          Map<String, Object> map = new HashMap<String, Object>(); 
          map.put("userId",5);     
          User user = mapper.getUser(map);
          System.out.println(user);     
          map.put("userId",5);     
          map.put("userName","孙悟空"); 
          map.put("password","565464");   
          if (mapper.addUser(map)) sqlSession.commit();      sqlSession.close();  }
    

3.6 模糊查询

  • java执行的时候,传递通配符% %

  • UserMapper.java

      //模糊查询  List<User> getUsersLike(String value);
    
  • UserMapper.xml

      <select id="getUsersLike" resultType="com.qian.pojo.User">    
          select * from mybatis.user where name like #{value}; 
      </select>
    
  • Test.java

      @Test  public void getUsersLike(){      
          UserMapper mapper = getUserMapper();    
          List<User> userList = mapper.getUsersLike("%千%");  
          System.out.println(userList);  
      }  public UserMapper getUserMapper(){  
          return MybatisUtils.getSqlSession().getMapper(UserMapper.class); 
      }
    
  • 在sql中使用拼接符

      <select id="getUsersLike" resultType="com.qian.pojo.User">    
      select * from mybatis.user where name like "%"#{value}"%"  
      </select>
    

4 配置解析

4.1 核心配合文件

  • mybatis-config.xml

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

      configuration(配置)  
      properties(属性)  
      settings(设置)  
      typeAliases(类型别名)  
      environments(环境配置)      
      environment(环境变量)          
      transactionManager(事务管理器)          
      dataSource(数据源)
      mappers(映射器)
    

​ 在Mybatis的配置文件包中是按照从上往下的级别进行排序

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境,尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fb9SBZZV-1671198315153)(C:\Users\卢申澳\AppData\Local\Temp\1670478859370.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feMhtewi-1671198315154)(C:\Users\卢申澳\AppData\Local\Temp\1670478849647.png)]

MyBatis默认事务连接器就是JDBC,连接池POOLED

  • 数据源(dataSource)
    作用:连接数据库
    c3p0 druid dbcp
    大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
    有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):

4.3 属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

  • 配置db.properties

      driver=com.mysql.jdbc.Driver 
      url=jdbc:mysql://localhost:3306/mybatis?       useSSL=false&;useUnicode=true&;characterEncoding=UTF8&;serverTimezone=GMT%2B8&;autoConnect=true  
      username=root  
      password=root
    
  • 在核心配置文件中引入

    注意:在xml中,所有的标签都可以规定顺序

     (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
    
  • 核心文件配置

      <configuration>     
      <properties resource="db.properties"/>   
      <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> 
          </configuration>
    
  • 总结

    • 可以直接引入外部文件
    • 也可以在里面配置
    • 外部引入的文件(db.properties)的优先级要比在要高
  • 错误提示
    img

4.4 类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

  • 在mybatis-config.xml -> 配置

       <!-- 第一种:直接给类取别名 用于实体类较少 -->  
    <typeAliases>  
        <typeAlias type="com.yu.pojo.User" alias="user"> 
        </typeAlias>   
        
      <!-- 第二种:直接扫描一个包目录下 默认名为类的小写 -->   <!-- 如需自定义名在实体类中 @Alias--> 
     <typeAliases>  
      <package name="com.lsa.pojo"/> 
     </typeAliases>
        
    
  • 如果使用第二种扫描包目录下的方式那么返回值后面设置的就是该类名字的小写

  • 然后xxxMapper.xml的返回值(resultType)就可以替换为resultType user

  • 实体类较少的时候使用第一种,较多就直接扫描包目录

  • 第一种可以自定义取别名

  • 第二种也可以用注解@Alias(“xxx”)给类起别名

4.5 设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • 需要掌握的设置
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7U7sxVUo-1671198315159)(C:\Users\卢申澳\AppData\Local\Temp\1670478962149.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BvbarujM-1671198315159)(C:\Users\卢申澳\AppData\Local\Temp\1670478978902.png)]

4.6 映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

每一个mapper.xml都需要在Mybatis核心配置文件中注册

几种绑定方式

方式一:【推荐使用】

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

注意点:

接口和它的Mapper配置文件必须同名!
接口和它的Mapper配置文件必须在同一个包下!
方式三:使用扫描包进行注入绑定

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <package name="com.kuang.dao"/>
</mappers>
  • class与扫描包(packaaage)绑定的注意点
    • 接口和Mapper配置文件必须同名
    • 接口和他的Mapper配置文件必须在同一包下

4.7 其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    img

4.8 生命周期和作用域(Scope)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDbNvHD5-1671198315161)(C:\Users\卢申澳\AppData\Local\Temp\1670479281065.png)]

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

SqlSessionFactoryBuilder

  • 局部变量:一旦创建了 SqlSessionFactory,就不再需要它了。

SqlSessionFactory

  • 全局变量:一旦被创建就应该在应用的运行期间一直存在,可以想象为数据库连接池,没有任何理由丢弃它或重新创建另一个实例。
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式。
  • 说白了就是可以想象为:数据库连接池

SqlSession

  • 连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要关闭
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9aHMhSEX-1671198315161)(C:\Users\卢申澳\AppData\Local\Temp\1670479263794.png)]

每一个Mapper代表一个具体的业务

5 解决属性名和字段名不一致

5.1 新建项目mybatis-03

  • 复制原文件到对应目录下,修改属性(ctrl+r)pwd->password
    img

  • 使用测试类测试
    img

  • UserMapper.xml

      <mapper namespace="com.yu.dao.UserMapper">  
          <select id="getUserById" parameterType="int" resultType="user">    
              select * from mybatis.user where id=#{id}      
              <!--   这句代码的本质:select id,name,pwd from ... 类型处理器找不到对应字段的属性,无法赋值  -->     
          </select>
    </mapper>
    

5.2 解决方法

  • 起别名

      select id,name,pwd as password from mybatis.user where id=#{id}
    
  • 使用resultMap
    resultMap:结果集映射

      <!--column数据库的列名 property实体类名-->
        <resultMap id="UserMap" type="User">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="pwd" property="password"/>
        </resultMap>
        <!-- 通过id查询-->
        <select id="getUserById" parameterType="int" resultMap="UserMap">
            select * from user where id = #{id}
        </select>
    

    ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

    ResultMap 的优秀之处——你完全可以不用显式地配置它们。

    虽然上面的例子不用显式配置 ResultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

6 日志

6.1 日志工厂

如果数据库操作出现异常,需要通过日志获取sql语句,方便纠错。

  • logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

    • SLF4J +
    • LOG4J 【掌握】
    • | LOG4J2
    • | JDK_LOGGING
    • | COMMONS_LOGGING
    • | STDOUT_LOGGING 【掌握】
    • | NO_LOGGING
  • 在mybatis-config.xml中配置设置

    STDOUT_LOGGING 标准日志输出

      <configuration>  
          <properties resource="db.properties"/>   
          <settings>    
              <setting name="logImpl" value="STDOUT_LOGGING"/>   
          </settings>   
          ... ... 
          <configuration>
    

6.2 测试输出

  • STDOUT_LOGGING

      Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.  /*      ...  */  Created connection 471579726.  Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  ==>  Preparing: select * from mybatis.user where id=?   ==> Parameters: 1(Integer)  <==    Columns: id, name, pwd  <==        Row: 1, 千树, 123  <==      Total: 1  User{id=1, name='千树', password='123'}  Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  Returned connection 471579726 to pool.
    

    从上面的输出可以看出,mybatis本质上是封装了JDBC

  • LOG4J

      ......  Caused by: org.apache.ibatis.exceptions.PersistenceException:   ### Error building SqlSession.  ### The error may exist in SQL Mapper Configuration  ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.logging.LogException: Error setting Log implementation.   ......  Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.logging.LogException: Error setting Log implementation.  Cause: java.lang.NoClassDefFoundError: org/apache/log4j/Priority  ...
    

    使用另一个标准就会报错,说明需要配置另外的东西

    解决方法:配置maven导入log4j

    pom.xml

      <!-- https://mvnrepository.com/artifact/log4j/log4j --> 
    <dependency>    
        <groupId>log4j</groupId> 
        <artifactId>log4j</artifactId>  
        <version>1.2.17</version> 
    </dependency>
    

    然后输出为正常输出了:

      log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).  log4j:WARN Please initialize the log4j system properly.  log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.  User{id=1, name='千树', password='123'}
    

6.3 LOG4J

什么是Log4J

  • log4j简介

  • Log4j是一个由Java编写可靠、灵活的日志框架,是Apache旗下的一个开源项目;现如今,Log4j已经被移植到了C、C++、Python等语言中,服务更多的Developer;

  • 使用Log4j,我们更加方便的记录了日志信息,它不但能控制日志输出的目的地,也能控制日志输出的内容格式;

  • 通过定义不同的日志级别,可以更加精确的控制日志的生成过程,从而达到我们应用的需求;

  • 这一切,都得益于一个灵活的配置文件,并不需要我们更改代码

  • 1.先导入log4j的包

  •        <dependencies>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
               
    
  • 2.log4j.properties

    
    log4j.rootLogger=DEBUG,console,file 
    #控制台输出 console appender
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target=System.out  
    log4j.appender.console.threshold=DEBUG
    log4j.appender.console.layout=org.apache.log4j.PatternLayout   
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n  
    #文件输出 rolling file appender
    log4j.appender.file=org.apache.log4j.RollingFileAppender 
    log4j.appender.file.File=./log/lsa.log
    log4j.appender.file.MaxFileSize=10mB
    log4j.appender.file.threshold=DEBUG   
    log4j.appender.file.layout=org.apache.log4j.PatternLayout   
    log4j.appender.file.MaxBackupIndex=2  
    log4j.appender.file.layout.ConversionPattern=%d{mmm d,yyyy hhss a} : %p [%t] %m%n   
    #日志输出级别 logger
    log4j.logger.org.mybatis=DEBUG  
    log4j.logger.java.sql=DEBUG 
    log4j.logger.java.sql.Statement=DEBUG 
    log4j.logger.java.sql.ResultSet=DEBUG 
    log4j.logger.java.sql.PreparedStatement=DEBUG 
    

    3.配置Log4j的实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    

  • 最终测试输出
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4CE3U9F-1671198315163)(C:\Users\卢申澳\AppData\Local\Temp\1669540738883.png)]

  • 简单的使用

      public class UserDaoTest {   
          static Logger logger = Logger.getLogger(UserDaoTest.class);   
          @Test   
          public void loggerTest(){     
              logger.info("info:进入了TestLog4j");   
              logger.debug("debug:调试");     
              logger.error("error:错误");   
              logger.fatal("fatal:致命错误");   
          } 
      }
    
    • 导包:import org.apache.log4j.Logger;

    • 获取日志对象,参数为当前类的class

    • 设置日志级别

      7 分页

      为什么要分页?

​ 答:减少数据处理量

7.1 limit分页

  • 语法

             select * from xxx limit startIndex,pageSize 
             select * from user limit 3;
    
  • mybatis的sql语句如果有多个参数,需要用map封装

  • 接口 UserMapper.java

      List<User> selectLimit(Map<String,Integer> map);
    
  • Mapper.xml

      <select id="selectLimit" parameterType="map" resultMap="UserMap">    
      select * from mybatis.user limit #{startIndex},#{pageSize} 
      </select>
    
  • 测试

      public class UserDaoTest {  
          @Test   
          public void limitTest(){  
              SqlSession sqlSession = MybatisUtils.getSqlSession();          UserMapper mapper = sqlSession.getMapper(UserMapper.class);  
              Map<String, Integer> map = new HashMap<String, Integer>();   
              map.put("startIndex",0);          map.put("pageSize",2);  
              List<User> list=mapper.selectLimit(map);  
              for (User u:               list) { 
                  System.out.println(u);    
              }          sqlSession.close();      }  }
    

    7.2 RowBounds分页

    这种方法不推荐使用,因为官方不推荐

  • 接口

      List<User> selectRowBounds();
    
  • 配置

      <select id="selectRowBounds" resultMap="UserMap">   
      select * from mybatis.user 
      </select>
    
  • 测试

      @Test
    public void selectRowBounds(){
          SqlSession sqlSession = MybatisUtils.getSqlSession(); 
          RowBounds rowBounds = new RowBounds(0,2); 
          List<User> list = sqlSession.selectList("com.yu.dao.UserMapper.selectRowBounds",null,rowBounds);   
          for (User user : list) { 
              System.out.println(user); 
          }    
          sqlSession.close(); 
      }
    

7.3 分页插件

MyBatis分页插件 PageHelper

  • 网站:点击访问
  • 网站有详细的文档,此处不在介绍,了解即可,需要用的时候再学

8 使用注解开发

注解的本质是使用反射,底层是代理模式(见设计模式)

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

8.1 注解查找

  • 接口

      @Select("select * from mybatis.user")  
       List<User> selectAll();
    
  • 注册绑定

      <mappers>    
      <mapper class="com.yu.dao.UserMapper"/>
      </mappers>
    
  • 测试

      @Test  public void selectAll(){   
          SqlSession sqlSession = MybatisUtils.getSqlSession();      //底层主要应用反射    
          UserMapper mapper = sqlSession.getMapper(UserMapper.class); 
          List<User> list=mapper.selectAll();  
          for (User user : list) {      
              System.out.println(user);
          }     
          sqlSession.close();
      }
    

8.2 注解CRUD

  • 设置自动提交

      public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
    
  • Mapper

      //多个参数情况下,有两种解决方式,一个map封装,另一种是注解Param
    @Select("select * from mybatis.user where id=#{id}") 
    User selectUserById(@Param("id") int id); 
    @Select("select * from mybatis.user")
    List<User> selectAll(); 
    @Insert("insert into mybatis.user() values(#{id},#{name},#{password}) ")  
    boolean insertUser(User u); 
    @Update("update user set name=#{name},pwd=#{password} where id = #{id}") 
    boolean updateUser(User u); 
    @Delete("delete from mybatis.user where id=#{id}") 
    boolean deleteUser(@Param("id") int id);
    
  • Test

        @Test
        public void getUserById() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userById = mapper.getUserById();
            for (User user : userById) {
                System.out.println(user);
            }
        }
    User{id=1, name='张三', password='null'}
    User{id=2, name='李四', password='null'}
    User{id=3, name='王五', password='null'}
    User{id=4, name='六六六', password='null'}
    User{id=5, name='7777', password='null'}
    User{id=6, name='李六', password='null'}
    
    
    

8.3 @Param注解

  • 基本类型的参数和String类型,需要加上这个注解

  • 引用类型不需要加

  • 如果只有一个基本类型的参数,可以省略

  • 我们在sql中引用的就是@Param(“xxx”)中设定的属性名

  • 如果@parm后面定义的什么,匹配的时候就应该是什么

  • @Select("select * from mybatis01.user where id = #{id2}")
    List<User> getUserByIdandname(@Param("id") int id2);
    

9 MyBatis执行流程

  1. Resource获取加载全局配置文件

  2. 实例化SqlSessionFactoryBuilder构造器

  3. 解析配置文件流XMLConfigBuilder

  4. Configuration所有的配置信息

  5. SqlSessionFactory实例化

  6. transaction事务管理器

  7. 创建executor执行器

  8. 创建sqlSession

  9. 实现CRUD

  10. 查看是否执行成功

  11. 提交事务

  12. 关闭sqlSession连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WoRsVkRV-1671198315164)(C:\Users\卢申澳\AppData\Local\Temp\1669628502633.png)]

10 Lombok

Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。

10.1 安装方法

  1. 左上角File->Settings->Plugins

  2. 搜索Lombok,下载安装

  3. 导入maven

     <dependency>     <groupId>org.projectlombok</groupId>     <artifactId>lombok</artifactId>     <version>1.18.10</version> </dependency>
    

10.2 使用Lombok

  • Lombok的支持

      @Getter and @Setter  @FieldNameConstants  @ToString  @EqualsAndHashCode  @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor  @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog  @Data  @Builder  @SuperBuilder  @Singular  @Delegate  @Value  @Accessors  @Wither  @With  @SneakyThrows  @val  @var  experimental @var  @UtilityClass  Lombok config system  Code inspections  Refactoring actions (lombok and delombok)
    
  • 常用支持

      @Data支持: 无参构造,getter&setter,toString,hashCode,equals  @AllArgsConstructor: 有参构造  @NoArgsConstructor: 无参构造
    
  • 使用方法,在实体类上加注解

11 多对一(一对多)的处理

  • 多个学生对应一个老师

  • 学生关联老师,多对一

  • 老师管理集合,一对多

  • Sql建表

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sV8ALMPw-1671198315165)(C:\Users\卢申澳\AppData\Local\Temp\1669628628585.png)]

      create table `teacher`
      (  `id` int not null, 
       `name` varchar(30) default null, 
       primary key(`id`) 
      ) engine=InnoDB default charset=utf8; 
      insert into teacher values (1,'王老师'); 
      create table `student`( 
          `id` int not null, 
          `name` varchar(30) default null,
          `tid` int not null,  primary key(`id`),
          key `FK_tid` (`tid`),  
          constraint `FK_tid` foreign key(`tid`) references `teacher`(`id`)  ) engine=InnoDB default charset=utf8;
    

11.1 测试环境搭建

  1. 导入Lombok
  2. 新建Teacher,Student实体类
  3. 新建Mapper接口
  4. 在resources新建com->xxx->dao文件夹
  5. 新建xxxMapper.xml文件
  6. 在mybatis-config.xml中注册绑定xxxMapper.xml
  7. 在TeacherMapper接口中创建selectAll()方法
  8. 在TeacherMapper.xml中写对应的查询
  9. 新建测试类,在测试类中测试使用

11.2 按照查询嵌套处理

  • 代码演示

      @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student { 
        private int id;   
        private String name;   
        private Teacher teacher; 
    }
    
      @Data 
    @AllArgsConstructor
    @NoArgsConstructor
    public class Teacher {   
        private int id; 
        private String name; 
    }
    
      List<Student> selectAll();
    
        <!--  查询思路:1.查询所有学生  2.根据查询出的学生的tid查询老师,子查询   --> 
    <resultMap id="student_teacher" type="Student"> 
        <!-- property是实体类的属性 column是数据库的字段 -->  
        <result property="id" column="id"/> 
        <result property="name" column="name"/>    
        <!-- 复杂的属性,需要单独处理,对象:association 集合collection  -->     
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>  
    </resultMap> 
    <select id="selectAll" resultMap="student_teacher"> 
        select * from mybatis.student 
    </select>
    <select id="getTeacher" resultType="Teacher">   
            select * from mybatis.teacher where id=#{tid}
    </select>
    
       @Test  public void selectAll(){   
           SqlSession sqlSession = MybatisUtils.getSqlSession();  
           StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);      List<Student> studentList = mapper.selectAll();
           for (Student s:           studentList) {  
               System.out.println(s);
           }      
           sqlSession.close(); 
       }
    

11.3按照结果嵌套处理

  • 代码演示

      List<Student> selectAll2();
    
      <select id="selectAll2" resultMap="S_T">   
          select s.id sid,s.name sname,t.name tname 
          from mybatis.student s,mybatis.teacher t      where s.tid=t.id
    </select> 
    <resultMap id="S_T" type="Student">   
              <result property="id" column="sid"/> 
              <result property="name" column="sname"/>  
              <association property="teacher" javaType="Teacher">    
             <result property="name" column="tname"/>    
              </association> 
    </resultMap>
    
      //测试代码
    

11.4 回顾Mysql多对一查询方式

  • 子查询
  • 联表查询

11.5 一对多的处理

  • 代码演示

      @Data
    @AllArgsConstructor 
    @NoArgsConstructor 
    public class Teacher {    
        private int id;   
        private String name;   
        //老师拥有多个学生  
        private List<Student> students; 
    }
    
      @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
        private int id;  
        private String name;
        private int tid; 
    }
    
      public interface TeacherMapper { 
          List<Teacher> selectAll();  
          //获取指定老师下的所有学生   
          Teacher getTeacher(@Param("tid")int id); 
          Teacher getTeacher2(@Param("tid")int id); 
          List<Student> getStudents(@Param("tid")int id);  }
    

    按照结果进行查询

       <select id="selectAll" resultType="Teacher">  
           select * from mybatis.teacher 
    </select> 
    <select id="getTeacher" resultMap="S_T"> 
        select t.id tid, t.name tname,s.name sname 
        from mybatis.teacher t,mybatis.student s    
        where s.tid=tid and tid=#{tid} 
    </select>
    <resultMap id="S_T" type="Teacher">  
        <result property="id" column="tid"/> 
        <result property="name" column="tname"/>  
        <!-- 集合中的泛型信息,我们使用ofType -->   
        <collection property="students" ofType="Student">  
            <result property="name" column="sname"/>  
            <result property="tid" column="tid"/>    
        </collection> 
    </resultMap> 
    ===============================================================================
    
    

    按照查询嵌套处理

       <select id="getTeacherById3" resultMap="Teacher_Student3">
            select * from mybatis01.teacher where id = #{tid}
        </select>
        <resultMap id="Teacher_Student3" type="Teacher">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <collection property="studentList" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
        </resultMap>
        <select id="getStudent" resultType="Student">
            select * from mybatis01.student where tid = #{id}
        </select>
    

11.6 小结

  • 关联 association 多对一 javatype
  • 集合 collection 一对多 oftype
  • 复杂的属性,需要单独处理,对象:association 集合collection
  • 集合中的泛型信息,我们使用ofType
  • javaType 指定实体类中属性的类型
  • ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点:

  • 保证sql语句的可读性
  • 注意一对多和多对一属性和字段的问题
  • 面试要点
    • Mysql引擎
    • InnoDB底层原理
    • 索引
    • 索引优化

12 动态SQL

什么是动态SQL?

动态SQL是指根据不同的条件生成不同的SQL语句

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

12.1 搭建环境

  • 搭建数据库

      create table blog(  
          id varchar(50) not null comment '博客id',
          title varchar(100) not null comment '博客标题', 
          author varchar(30) not null comment '博客作者', 
          ctreate_time datetime not null comment '创建时间',
          views int not null comment '浏览量' 
      )engine=InnoDB default charset=utf8;
    

创建基础工程

  • 导包

  • 编写配置文件

    mybatis-config 与MybatisUtils

  • 编写实体类日

      import java.util.Date; 
    @Data 
    @AllArgsConstructor
    @NoArgsConstructor 
    public class Blog {  
          private String id;  
          private String title;  
          private String author; 
          private Date createTime; 
          private int views; 
    }
    
  • 编写实体类对应Mapper接口和Mapper.xml文件

      public interface BlogMapper {}
    
      <?xml version="1.0" encoding="UTF-8" ?>  <!DOCTYPE mapper          PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
    <mapper namespace="com.yu.dao.BlogMapper">
    </mapper>
    
  • 解决数据库字段名和实体类属性名不一致的问题

      <!--       有两种解决方法:     
         1.如果不是按规范转换,在xxxMapper.xml用ResultMap,上面已经介绍过      
         2.如果是规范命名,在mybatis-config.xml文件中<settings>-><setting>->id="mapUnderscoreToCamelCase" value="true",它的作用是驼峰命名转换 
         3.直接在查询的列名中起别名-->  
    <settings>   
          <setting name="logImpl" value="STDOUT_LOGGING"/> 
          <setting name="mapUnderscoreToCamelCase" value="true"/>  
    </settings>
    

12.2 if

  • 使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

      <select id="findActiveBlogWithTitleLike"   
              resultType="Blog"> 
          SELECT * FROM BLOG    WHERE state = ‘ACTIVE’  
          <if test="title != null">     
              AND title like #{title}   
          </if> 
    </select>
    
  • 代码演示

      //查询博客
    List<Blog> queryBlogIf(Map<String,Object> map);
    
      <select id="queryBlogIf" parameterType="map" resultType="Blog">  
          select * from mybatis.blog where 1=1  
          <if test="title!=null">     
              and title = #{title}    
          </if>   
          <if test="author!=null"> 
             and author = #{author}   
          </if> 
    </select>
    
    方法二带有where 可以帮你取消and 或不符合的sql
    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
            <!--id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <if test="title !=null">
                    and title = #{title}
                </if>
                <if test="author !=null">
                    and author = #{author}
                </if>
            </where>
        </select>
    
    
    ```java
      @Test
        public void queryBlogByIf() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map map = new HashMap();
            //map.put("title", "Mybatis那么简单");
            map.put("author", "卢哥说");
            List<Blog> blogList = mapper.queryBlogByIf(map);
            for (Blog blog : blogList) {
                System.out.println(blog);
            }
            sqlSession.close();
        }
    

12.3 choose、when、otherwise

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

  • 还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。

  • 代码演示

        List<Blog> queryBlogByChoose(Map map);
    
     <select id="queryBlogByChoose" parameterType="map" resultType="Blog">
            <!--        id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <choose>
                    <when test="title !=null">
                         title = #{title}
                    </when>
                    <when test="author !=null">
                        and author = #{author}
                    </when>
                    <otherwise>
                        and views = #{views}
                    </otherwise>
                </choose>
            </where>
        </select>
    
        
        @Test
        public void queryBlogByChoose() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map map = new HashMap();
            map.put("title", "Mybatis那么简单");
            map.put("author", "卢哥说");
            map.put("views", 999);
            List<Blog> blogList = mapper.queryBlogByChoose(map);
            for (Blog blog : blogList) {
                System.out.println(blog);
            }
    
            sqlSession.close();
        }
    他只符合你的第一条,如果第一条满足不会往下进行否则反之
    

12.4 trim、where、set

  • 前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题

  • where

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 就是只有下面if成立才启动where 且可以帮你取出and 或 or

  • 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

  •   <trim prefix="WHERE" prefixOverrides="AND |OR ">    ...  </trim>
    
    
        List<Blog> queryBlogByIf(Map map);
        
            
            <select id="queryBlogByIf" parameterType="map" resultType="Blog">
            <!--        id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <if test="title !=null">
                    and title = #{title}
                </if>
                <if test="author !=null">
                    and author = #{author}
                </if>
            </where>
        </select>
    
    
    

  • set

  • 这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

  • prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容

  • 用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

  • 来看看与 set 元素等价的自定义 trim 元素吧:

  •  <trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
     prefix:在trim标签内 sql语句加上前缀。 就是在条件前加上
     suffix:在trim标签内 sql语句加上后缀。 就是在条件结束加上
     suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。
     prefixOverrides:指定去除多余的前缀内容
    
  •   
     int updateBlog(Map map);
       <update id="updateBlog" parameterType="map">
            update mybatis01.blog
            <set>
                <if test="title !=null">
                    title = #{title},
                </if>
                <if test="author !=null">
                    author = #{author}
                </if>
            </set>
            where id = #{id}
        </update>
       </select>
    

12.5 SQL片段

  • 所谓动态sql,本质还是SQL语句,只是我们可以在SQL层面,执行逻辑代码

  • 有的时候,我们可以将一些功能的部分抽取出来(类似函数的封装),方便复用

     1.使用SQL标签抽取公共部分
    
    title = #{title} and author = #{author} ```

    2.在需要使用的地方使用Include标签引用即可

  • 使用sql标签封装,在需要的地方使用include标签引入

    <select id="queryBlogIf" parameterType="map" resultType="Blog">  
        select * from mybatis.blog   
        <where>  
            <include refid="if-title-author">
            </include> 
            <!--      <if test="title!=null">          title = #{title}      </if>      <if test="author!=null">          and author = #{author}      </if>      -->      </where> 
    </select>
    
  • 注意事项:

    • 最好基于单表定义SQL片段
    • 不要存在where标签

12.6 foreach

  • 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

      <select id="selectPostIn" resultType="domain.blog.Post">  
          SELECT *    FROM POST P    WHERE ID in   
          <foreach item="item" index="index" collection="list"  
                  open="(" separator="," close=")">  
              #{item}  
          </foreach>  </select>
    
  • foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

  • 提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

  • 至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

  • foreach的作用,就是为了替代下面这种复杂的语句

    <select id="queryBlogByForeach" parameterType="map" resultType="Blog">
            select * from mybatis01.blog
            <where>
                <!--elect * from mybatis01.blog where id  = IN (1 OR 2 OR 3)-->
                <foreach collection="ids" item="id" open=" in (" close=")" separator="or">
                    id = #{id}
                </foreach>
            </where>
        </select>
    其中item 后的参数就相当于 sql where后面得数据线
    
  • 代码演示(改一下数据库id)

      List<Blog> queryBlogForeach(Map<String,Object> map);
    
      <select id="queryBlogForeach" parameterType="map" resultType="Blog">      select * from mybatis.blog      <where>          <foreach collection="ids" item="id" open="(" close=")" separator="or">              id=#{id}          </foreach>      </where>  </select>
    
       @Test  public void queryBlogForeach(){      SqlSession sqlSession = MybatisUtils.getSqlSession();      BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);      Map<String,Object> map=new HashMap<String,Object>();      List<Integer> ids = new ArrayList<Integer>();//        ids.add(1);      ids.add(2);      ids.add(3);      map.put("ids",(List)ids);      List<Blog> blogs = mapper.queryBlogForeach(map);      sqlSession.close();  }
    

    动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式去排列组合

    建议:

    1. 现在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可

13 缓存

13.1 简介

  • 为什么要使用缓存

    每次查询都要连接数据库,比较耗资源,我们把查询到的数据暂存到内存里面,下次查询的时候,从内存读取, 这个地方就叫缓存。

  • 什么样的数据适用于缓存?

    经常查询且不经常改变的数据

13.2 Mybatis缓存

  • Mybatis系统默认定义了两级缓存
    • 默认情况下,只有一级缓存开启(SqlSession缓存,也称为本地缓存)
    • 二级缓存需要手动配置,它是基于namespace级别的缓存
    • Mybatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存

13.3 一级缓存

  • 测试步骤

    1. 开启日志

    2. 测试在一个Session中查询两次的计量局

    3. 查看日志输出

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRtPeGSB-1671198315165)(C:\Users\卢申澳\AppData\Local\Temp\1669888868331.png)]

      sql查询出来一次

  • 代码演示

        User getUserById(@Param("id") int id);
    
     <select id="getUserById" parameterType="int" resultType="User">
            select * from mybatis01.user where id = #{id}
        </select>
    
        @Test
        public void userTest() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User userById = mapper.getUserById(1);
            System.out.println(userById);
            System.out.println("===========================");
    
            User userById2 = mapper.getUserById(1);
            System.out.println(userById);
    
            System.out.println(userById == userById2);
            sqlSession.close();
        }
    

  • 缓存失效的情况

    1. 查询不同的xxxMapper.xml

    2. 增删改

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLrGqo9s-1671198315166)(C:\Users\卢申澳\AppData\Local\Temp\1669890361073.png)]

    3. 查询不同的东西

    4. 手动清理缓存(sqlSession.clearCache())

      @Test
      public void updateUser() {
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          User userById = mapper.getUserById(1);
          System.out.println(userById);
      
          System.out.println("===========================");
          mapper.updateUser(new User(2, "张四111", "123456789"));
              sqlSession.clearCache();
          System.out.println("===========================");
          User userById2 = mapper.getUserById(1);
          System.out.println(userById2);
      
          System.out.println(userById == userById2);
          sqlSession.close();
      }
      

​ 小结:一级缓存默认是开启的,只在一次sqlsession中有效 ,也就是拿到链接关闭链接

一级缓存就是map

13.4 二级缓存

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

  • 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

      <cache/>
    
    
         <!--显示的开启全局缓存-->
            <setting name="cacheEnabled" value="true"/>
    
  • 基本上就是这样。这个简单语句的效果如下:

  • 这些属性可以通过 cache 元素的属性来修改。比如:

      <cache  
             eviction="FIFO"   
             flushInterval="60000"   
             size="512" 
             readOnly="true"/>
    
  • 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

  • 使用二级缓存步骤:

    1. 开启全局缓存

      <!-- 虽然默认开启,但是写上可以让看代码的人明白 -->
      <setting name="cacheEnabled" value="true"/>
      
    2. 在要使用二级缓存的Mapper.xml中,写标签

      <cache 
             eviction="FIFO" 
             flushInterval="60000"
             size="512"
             readOnly="true"/>
      
    3. 测试使用

        @Test
          public void updateUser2() {
              SqlSession sqlSession = MybatisUtils.getSqlSession();
              SqlSession sqlSession2 = MybatisUtils.getSqlSession();
      
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              User userById = mapper.getUserById(1);
              System.out.println(userById);
              sqlSession.close();
      
              System.out.println("===========================");
              UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
              User userById2 = mapper2.getUserById(1);
              System.out.println(userById2);
      
          System.out.println(userById == userById2);
          sqlSession2.close();
      }
      
      
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vF2qSeYD-1671198315166)(C:\Users\卢申澳\AppData\Local\Temp\1669892021179.png)]
      
      
  • 问题

    • 我们需要实体类序列化,否则会抛出异常
  • 小结

    • 二级缓存在同一个Mapper下有效
    • 所有的数据都会先放在一级缓存中
    • 当会话提交或者关闭,数据会被转存到二级缓存中

13.5 缓存原理

  • 图片演示
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uEWExUPH-1671198315167)(C:\Users\卢申澳\AppData\Local\Temp\1670479127194.png)]

缓存顺序

1.先看二级缓存中有没有

2.再看一级缓存中有没有

3.查询数据库

13.6 自定义缓存ehcache

  • 简介

    EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

  • 使用

    1. 导包

      <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --><dependency>
          <groupId>org.mybatis.caches</groupId> 
          <artifactId>mybatis-ehcache</artifactId>
          <version>1.2.2</version>
      </dependency>
      
    2. 写入配置文件(resources->ehcache.xml)

      <?xml version="1.0" encoding="UTF-8"?>
      <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"><!-- 磁盘缓存位置 -->
          <diskStore path="java.io.tmpdir/ehcache"/><!-- 默认缓存 -->
          <defaultCache
                  maxEntriesLocalHeap="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  maxEntriesLocalDisk="10000000"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU">
              <persistence strategy="localTempSwap"/>
          </defaultCache><!-- helloworld缓存 -->
          <cache name="HelloWorldCache"
                 maxElementsInMemory="1000"
                 eternal="false"
                 timeToIdleSeconds="5"
                 timeToLiveSeconds="5"
                 overflowToDisk="false"
                 memoryStoreEvictionPolicy="LRU"/>
      </ehcache>
      
    3. 在Mapper中指定

      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
      
    4. 测试使用(用之前的代码即可)

  • 自定义缓存

    只要实现了org.apache.ibatis.cache.Cache接口,就能定义自己的缓存,但是实现比较复杂,只需要会使用就行,ehcache是继承了AbstractEhcacheCache,该类已经实现了Cache接口。

      public class MyCache implements Cache {      @Override      public String getId() {          return null;      }      @Override      public void putObject(Object key, Object value) {      }      @Override      public Object getObject(Object key) {          return null;      }      @Override      public Object removeObject(Object key) {          return null;      }      @Override      public void clear() {      }      @Override      public int getSize() {          return 0;      }  }
    
  • 实际开发中使用的缓存

    • 在实际开发中,我们更多的使用Redis来做缓存

Mybatis

环境

  • JDBC
  • java基础
  • JDK 1.8
  • Mysql5.7
  • maven 3.6.1
  • 开发工具 idea
  • Junit

SSM框架:配置文件的最好方式:看官网文档

1 简介

1.1什么是mybatis?

  • MyBatis是一款优秀的持久层框架
  • 支持定制SQL、存储过程以及高级映射
  • Mybatis避免了几乎所有的JDBC代码和手动设置参树,以及获取结果集
  • Mybatis可以使用简单的XML注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录
  • MyBatis本是apache的一个开源项目iBatis
  • 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis
  • 2013年11月迁移到Github

如何获得Mybatis?

  • Maven仓库:

    下载地址

      <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->  <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis</artifactId>      <version>3.5.2</version>  </dependency>
    
  • Github:Mybatis地址

  • 中文文档地址

1.2 持久化

数据持久化

  • 持久化就是将程序的数据保存在硬盘中
  • 内存特性:断电会失去信息
  • 数据库(jdbc),io文件持久化
  • 生活中的持久化:冷藏,写……

为什么需要持久化?

  • 我们需要将一些数据保存下来,方便以后使用
  • 内存价格过高(制造成本高)

1.3 持久层

复习学习过的层:Dao,Service,Controller

持久层的工作

  • 完成持久化工作的代码块
  • 层之间的接线非常明显

1.4 为什么需要Mybatis?

  • 帮助程序员将数据存入到数据库中
  • 传统的JDBC代码太复杂了
  • 简化、框架、自动化
  • 不用Mybatis也能实现,Mybatis可以使程序员在不知道底层原理的情况下,完成网站后台搭建
  • 优点:
    • 简单易学
    • 灵活
    • 解除sql与程序代码的耦合
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

2 第一个MyBatis程序

思路:搭建环境—>导入MyBatis—>编写代码—>测试

2.1 搭建环境

  • 搭建数据库

       -- 创建数据库   create database `mybatis`;   use mybatis;   -- 创建表   create table `user`(      `id` int(20) not null,      `name` varchar(30) default null,      `pwd` varchar(30) default null,      primary key(`id`)    )engine=InnoDB default charset=utf8mb4;   -- 插入数据   insert into `user`(`id`,`name`,`pwd`) values  (1,'千树','123'),  (2,'张三','123'),  (3,'李飞','123');
    
  • mybatis官方文档:文档地址

  • 新建普通maven项目作为父项目,导入sql驱动,mybatis,junit组件

      <!--导入依赖-->  <dependencies>   
          <!--mysql驱动-->   
          <dependency>   
              <groupId>mysql</groupId> 
              <artifactId>mysql-connector-java</artifactId>                                   <version>5.1.46</version>    
          </dependency>   
          <!--Mybatis-->  
          <dependency>      
              <groupId>org.mybatis</groupId>                                                 <artifactId>mybatis</artifactId>       
              <version>3.5.2</version>   
          </dependency>  
          <!--junit-->     
          <dependency>      
              <groupId>junit</groupId>  
              <artifactId>junit</artifactId>  
              <version>4.12</version>    
          </dependency> 
    </dependencies>
    
  • 新建新组件作为子级项目,普通maven的module

  • 添加配置文件:

    在src->main->resources目录下新建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}"/>//数据库名字,默认root                         <property name="password" value="${password}"/>//数据库密码,自己的数据库密码,一般为root        </dataSource>   
              </environment> 
          </environments> 
    </configuration>
    

    配置文件的作用就是连接数据库

2.2 创建模块

  • 编写mybatis工具类

      //SqlSessionFactory --生产--> SqlSession  public class MybatisUtils {      private static SqlSessionFactory sqlSessionFactory; //提升作用域      //获取工厂,固定代码      static {          try {              String resource="mybatis-config.xml";              InputStream inputStream = Resources.getResourceAsStream(resource);              sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);          } catch (IOException e) {              e.printStackTrace();          }      }      //获取sqlSession      //SqlSession完全包含了面向对象数据库执行SQL命令所需的方法      public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession();}  }
    

2.3 编写代码

  • 实体类

      public class User {      private int id;      private String name;      private String pwd;      public User() { }      public User(int id, String name, String pwd) {          this.id = id;          this.name = name;          this.pwd = pwd;      }      public int getId() {          return id;      }      public void setId(int id) {          this.id = id;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getPwd() {          return pwd;      }      public void setPwd(String pwd) {          this.pwd = pwd;      }      @Override      public String toString() {          return "User{" +                  "id=" + id +                  ", name='" + name + '\'' +                  ", pwd='" + pwd + '\'' +                  '}';      }  }
    
  • Dao接口

      public interface UserDao {      List<User> getUserList();  }
    
  • 接口实现类改为以xxxMapper.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">  <!--namespace:命名空间,绑定mapper/Dao接口-->  <mapper namespace="com.qian.dao.UserDao">  <!--id:接口的方法,resultType:接口的返回值类型-->      <select id="getUserList" resultType="com.qian.pojo.User">          select * from mybatis.user where id = #{id}      </select>  </mapper>
    

2.4 测试

  • 测试类

      public class UserDaoTest {      @Test      public void test(){          //获取SqlSession对象          SqlSession sqlSession = MybatisUtils.getSqlSession();          //获取mapper          UserDao mapper = sqlSession.getMapper(UserDao.class);          List<User> list = mapper.getUserList();          for (User u:list){              System.out.println(u);          }          //不推荐使用  /*      这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。  */  //        List<User> list = sqlSession.selectList("com.qian.dao.UserDao.getUserList");  //        for (User user : list) {  //            System.out.println(user);  //        }          //关闭SqlSession          sqlSession.close();      }  }
    

    异常1:org.apache.ibatis.binding.BindingException: Type interface com.qian.dao.UserDao is not known to the MapperRegistry.

    解决方法:每一个Mapper.xml文件都需要在src->main->resources目录下新建mybatis-config.xml的核心配置文件中注册

    <mappers>  <mapper resource="com/qian/dao/UserMapper.xml"></mappers>
    

    异常2:
    Error building SqlSession.
    The error may exist in com/qian/dao/UserMapper.xml
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
    Cause: java.io.IOException: Could not find resource com/qian/dao/UserMapper.xml

    解决方法:

    <!-- 在maven中,约定大于配置,在pom中添加此文件可以解决 --><build>  <resources>      <resource>          <directory>src/main/resources</directory>          <includes>              <include>**/*.properties</include>              <include>**/*.xml</include>          </includes>          <filtering>true</filtering>      </resource>      <resource>          <directory>src/main/java</directory>          <includes>              <include>**/*.properties</include>              <include>**/*.xml</include>          </includes>          <filtering>true</filtering>      </resource>  </resources></build>
    

    异常3:
    Error building SqlSession.
    The error may exist in com/qian/dao/UserMapper.xml
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
    Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 6;

    解决方法:

    <?xml version="1.0" encoding="UTF-8" ?>    <!-- 把mybatis-config.xml与mybatis-config.xml文件的encoding修改成下面的 --><?xml version="1.0" encoding="UTF8" ?>
    

    另一种解决方法:删除掉xxxMapper.xml文件中所有的中文注释

    异常4:
    Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

    解决方法:
    useSSL=true改为false(true也可以,需要在mysql中启用SSL)

    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=GMT%2B8"/>
    

2.5 总结

img

public class UserDaoTest {    @Test    public void test(){        //获取SqlSession对象        SqlSession sqlSession = MybatisUtils.getSqlSession();        try{            //获取mapper            UserDao mapper = sqlSession.getMapper(UserDao.class);            List<User> list = mapper.getUserList();            for (User u:list){                System.out.println(u);            }            //不推荐使用//        List<User> list = sqlSession.selectList("com.qian.dao.UserDao.getUserList");//        for (User user : list) {//            System.out.println(user);//        }        }finally {            //关闭SqlSession            sqlSession.close();        }    }}

3 增删改查实现

3.1 Mapper接口

public interface UserMapper {    //查询全部用户    List<User> getUserList();    //根据id查询用户    User getUserById(int id);    //增加新的用户    boolean insertNewUser(User u);    //删除用户    boolean deleteUserById(int id);    boolean deleteUserByName(String name);    //修改用户    boolean updateUserById(User u);}

3.2 xxxMapper.xml文件

<?xml version="1.0" encoding="utf8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:命名空间,绑定mapper/Dao接口--><!--id:接口的方法,resultType:接口的返回值类型--><mapper namespace="com.qian.dao.UserMapper">    <select id="getUserList" resultType="com.qian.pojo.User">        select * from mybatis.user    </select>    <select id="getUserById" parameterType="int" resultType="com.qian.pojo.User">        select * from mybatis.user where id=#{id}    </select>    <!-- 对象中的属性,可以直接取出来用 -->    <insert id="insertNewUser" parameterType="com.qian.pojo.User">        insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})    </insert>    <delete id="deleteUserById" parameterType="int">        delete from mybatis.user where id=#{id}    </delete>    <delete id="deleteUserByName" parameterType="String">        delete from mybatis.user where name=#{name}    </delete>    <update id="updateUserById" parameterType="com.qian.pojo.User">        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}    </update></mapper>

3.3 Test类

注意:增删改要提交事务!!!

public class UserDaoTest {    @Test    public void test(){        //获取SqlSession对象        SqlSession sqlSession = MybatisUtils.getSqlSession();        try{            //获取mapper            UserMapper mapper = sqlSession.getMapper(UserMapper.class);//            查询全表//            List<User> list = mapper.getUserList();//            for (User u:list){//                System.out.println(u);//            }            //根据id查询//            User user = mapper.getUserById(1);//            System.out.println(user);            //插入新用户,注意:更新,插入,删除都需要提交事务//            User user1 = new User(4,"李四","25615");//            boolean isInserted = mapper.insertNewUser(user1);//            sqlSession.commit();            //代码优化//            if (mapper.insertNewUser(new User(4,"李四","25615"))) sqlSession.commit();            //删除用户//            if (mapper.deleteUserById(4))sqlSession.commit();             if (mapper.deleteUserByName("李四"))sqlSession.commit();             //修改用户            if (mapper.updateUserById(new User(4,"王五","6849816")))sqlSession.commit();        }finally {            //关闭SqlSession            sqlSession.close();        }    }}

3.4 常见错误

  • 标签要匹配
  • resource绑定Mapper,需要使用路径
  • 配置文件中不要写注释
  • useSSL=true有时候无法使用,改为false

3.5 万能Map

  • UserMapper.java

      User getUser(Map<String,Object> map);  boolean addUser(Map<String,Object> map);
    
  • UserMapper.xml

      <select id="getUser" parameterType="map" resultType="com.qian.pojo.User">   
          select * from mybatis.user where id=#{userId} 
    </select> 
    <insert id="addUser" parameterType="map">  
        insert into mybatis.user (id, name, pwd) VALUES (#{userId},#{userName},#{password})  </insert>
    
  • Test.java

      @Test  public void test(){      //获取SqlSession对象   
          SqlSession sqlSession = MybatisUtils.getSqlSession();   
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);   
          Map<String, Object> map = new HashMap<String, Object>(); 
          map.put("userId",5);     
          User user = mapper.getUser(map);
          System.out.println(user);     
          map.put("userId",5);     
          map.put("userName","孙悟空"); 
          map.put("password","565464");   
          if (mapper.addUser(map)) sqlSession.commit();      sqlSession.close();  }
    

3.6 模糊查询

  • java执行的时候,传递通配符% %

  • UserMapper.java

      //模糊查询  List<User> getUsersLike(String value);
    
  • UserMapper.xml

      <select id="getUsersLike" resultType="com.qian.pojo.User">    
          select * from mybatis.user where name like #{value}; 
      </select>
    
  • Test.java

      @Test  public void getUsersLike(){      
          UserMapper mapper = getUserMapper();    
          List<User> userList = mapper.getUsersLike("%千%");  
          System.out.println(userList);  
      }  public UserMapper getUserMapper(){  
          return MybatisUtils.getSqlSession().getMapper(UserMapper.class); 
      }
    
  • 在sql中使用拼接符

      <select id="getUsersLike" resultType="com.qian.pojo.User">    
      select * from mybatis.user where name like "%"#{value}"%"  
      </select>
    

4 配置解析

4.1 核心配合文件

  • mybatis-config.xml

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

      configuration(配置)  
      properties(属性)  
      settings(设置)  
      typeAliases(类型别名)  
      environments(环境配置)      
      environment(环境变量)          
      transactionManager(事务管理器)          
      dataSource(数据源)
      mappers(映射器)
    

​ 在Mybatis的配置文件包中是按照从上往下的级别进行排序

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境,尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Grnu82H4-1671198316513)(C:\Users\卢申澳\AppData\Local\Temp\1670478859370.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OME3jw58-1671198316514)(C:\Users\卢申澳\AppData\Local\Temp\1670478849647.png)]

MyBatis默认事务连接器就是JDBC,连接池POOLED

  • 数据源(dataSource)
    作用:连接数据库
    c3p0 druid dbcp
    大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
    有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):

4.3 属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

  • 配置db.properties

      driver=com.mysql.jdbc.Driver 
      url=jdbc:mysql://localhost:3306/mybatis?       useSSL=false&;useUnicode=true&;characterEncoding=UTF8&;serverTimezone=GMT%2B8&;autoConnect=true  
      username=root  
      password=root
    
  • 在核心配置文件中引入

    注意:在xml中,所有的标签都可以规定顺序

     (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
    
  • 核心文件配置

      <configuration>     
      <properties resource="db.properties"/>   
      <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> 
          </configuration>
    
  • 总结

    • 可以直接引入外部文件
    • 也可以在里面配置
    • 外部引入的文件(db.properties)的优先级要比在要高
  • 错误提示
    img

4.4 类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

  • 在mybatis-config.xml -> 配置

       <!-- 第一种:直接给类取别名 用于实体类较少 -->  
    <typeAliases>  
        <typeAlias type="com.yu.pojo.User" alias="user"> 
        </typeAlias>   
        
      <!-- 第二种:直接扫描一个包目录下 默认名为类的小写 -->   <!-- 如需自定义名在实体类中 @Alias--> 
     <typeAliases>  
      <package name="com.lsa.pojo"/> 
     </typeAliases>
        
    
  • 如果使用第二种扫描包目录下的方式那么返回值后面设置的就是该类名字的小写

  • 然后xxxMapper.xml的返回值(resultType)就可以替换为resultType user

  • 实体类较少的时候使用第一种,较多就直接扫描包目录

  • 第一种可以自定义取别名

  • 第二种也可以用注解@Alias(“xxx”)给类起别名

4.5 设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • 需要掌握的设置
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rh9igFEx-1671198316514)(C:\Users\卢申澳\AppData\Local\Temp\1670478962149.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SB3bHxDn-1671198316515)(C:\Users\卢申澳\AppData\Local\Temp\1670478978902.png)]

4.6 映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

每一个mapper.xml都需要在Mybatis核心配置文件中注册

几种绑定方式

方式一:【推荐使用】

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

注意点:

接口和它的Mapper配置文件必须同名!
接口和它的Mapper配置文件必须在同一个包下!
方式三:使用扫描包进行注入绑定

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <package name="com.kuang.dao"/>
</mappers>
  • class与扫描包(packaaage)绑定的注意点
    • 接口和Mapper配置文件必须同名
    • 接口和他的Mapper配置文件必须在同一包下

4.7 其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    img

4.8 生命周期和作用域(Scope)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yzijwnnr-1671198316516)(C:\Users\卢申澳\AppData\Local\Temp\1670479281065.png)]

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

SqlSessionFactoryBuilder

  • 局部变量:一旦创建了 SqlSessionFactory,就不再需要它了。

SqlSessionFactory

  • 全局变量:一旦被创建就应该在应用的运行期间一直存在,可以想象为数据库连接池,没有任何理由丢弃它或重新创建另一个实例。
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式。
  • 说白了就是可以想象为:数据库连接池

SqlSession

  • 连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要关闭
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhCFOVgc-1671198316516)(C:\Users\卢申澳\AppData\Local\Temp\1670479263794.png)]

每一个Mapper代表一个具体的业务

5 解决属性名和字段名不一致

5.1 新建项目mybatis-03

  • 复制原文件到对应目录下,修改属性(ctrl+r)pwd->password
    img

  • 使用测试类测试
    img

  • UserMapper.xml

      <mapper namespace="com.yu.dao.UserMapper">  
          <select id="getUserById" parameterType="int" resultType="user">    
              select * from mybatis.user where id=#{id}      
              <!--   这句代码的本质:select id,name,pwd from ... 类型处理器找不到对应字段的属性,无法赋值  -->     
          </select>
    </mapper>
    

5.2 解决方法

  • 起别名

      select id,name,pwd as password from mybatis.user where id=#{id}
    
  • 使用resultMap
    resultMap:结果集映射

      <!--column数据库的列名 property实体类名-->
        <resultMap id="UserMap" type="User">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="pwd" property="password"/>
        </resultMap>
        <!-- 通过id查询-->
        <select id="getUserById" parameterType="int" resultMap="UserMap">
            select * from user where id = #{id}
        </select>
    

    ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

    ResultMap 的优秀之处——你完全可以不用显式地配置它们。

    虽然上面的例子不用显式配置 ResultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

6 日志

6.1 日志工厂

如果数据库操作出现异常,需要通过日志获取sql语句,方便纠错。

  • logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

    • SLF4J +
    • LOG4J 【掌握】
    • | LOG4J2
    • | JDK_LOGGING
    • | COMMONS_LOGGING
    • | STDOUT_LOGGING 【掌握】
    • | NO_LOGGING
  • 在mybatis-config.xml中配置设置

    STDOUT_LOGGING 标准日志输出

      <configuration>  
          <properties resource="db.properties"/>   
          <settings>    
              <setting name="logImpl" value="STDOUT_LOGGING"/>   
          </settings>   
          ... ... 
          <configuration>
    

6.2 测试输出

  • STDOUT_LOGGING

      Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.  /*      ...  */  Created connection 471579726.  Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  ==>  Preparing: select * from mybatis.user where id=?   ==> Parameters: 1(Integer)  <==    Columns: id, name, pwd  <==        Row: 1, 千树, 123  <==      Total: 1  User{id=1, name='千树', password='123'}  Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c1bbc4e]  Returned connection 471579726 to pool.
    

    从上面的输出可以看出,mybatis本质上是封装了JDBC

  • LOG4J

      ......  Caused by: org.apache.ibatis.exceptions.PersistenceException:   ### Error building SqlSession.  ### The error may exist in SQL Mapper Configuration  ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.logging.LogException: Error setting Log implementation.   ......  Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.logging.LogException: Error setting Log implementation.  Cause: java.lang.NoClassDefFoundError: org/apache/log4j/Priority  ...
    

    使用另一个标准就会报错,说明需要配置另外的东西

    解决方法:配置maven导入log4j

    pom.xml

      <!-- https://mvnrepository.com/artifact/log4j/log4j --> 
    <dependency>    
        <groupId>log4j</groupId> 
        <artifactId>log4j</artifactId>  
        <version>1.2.17</version> 
    </dependency>
    

    然后输出为正常输出了:

      log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).  log4j:WARN Please initialize the log4j system properly.  log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.  User{id=1, name='千树', password='123'}
    

6.3 LOG4J

什么是Log4J

  • log4j简介

  • Log4j是一个由Java编写可靠、灵活的日志框架,是Apache旗下的一个开源项目;现如今,Log4j已经被移植到了C、C++、Python等语言中,服务更多的Developer;

  • 使用Log4j,我们更加方便的记录了日志信息,它不但能控制日志输出的目的地,也能控制日志输出的内容格式;

  • 通过定义不同的日志级别,可以更加精确的控制日志的生成过程,从而达到我们应用的需求;

  • 这一切,都得益于一个灵活的配置文件,并不需要我们更改代码

  • 1.先导入log4j的包

  •        <dependencies>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
               
    
  • 2.log4j.properties

    
    log4j.rootLogger=DEBUG,console,file 
    #控制台输出 console appender
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target=System.out  
    log4j.appender.console.threshold=DEBUG
    log4j.appender.console.layout=org.apache.log4j.PatternLayout   
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n  
    #文件输出 rolling file appender
    log4j.appender.file=org.apache.log4j.RollingFileAppender 
    log4j.appender.file.File=./log/lsa.log
    log4j.appender.file.MaxFileSize=10mB
    log4j.appender.file.threshold=DEBUG   
    log4j.appender.file.layout=org.apache.log4j.PatternLayout   
    log4j.appender.file.MaxBackupIndex=2  
    log4j.appender.file.layout.ConversionPattern=%d{mmm d,yyyy hhss a} : %p [%t] %m%n   
    #日志输出级别 logger
    log4j.logger.org.mybatis=DEBUG  
    log4j.logger.java.sql=DEBUG 
    log4j.logger.java.sql.Statement=DEBUG 
    log4j.logger.java.sql.ResultSet=DEBUG 
    log4j.logger.java.sql.PreparedStatement=DEBUG 
    

    3.配置Log4j的实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    

  • 最终测试输出
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WisA3c50-1671198316517)(C:\Users\卢申澳\AppData\Local\Temp\1669540738883.png)]

  • 简单的使用

      public class UserDaoTest {   
          static Logger logger = Logger.getLogger(UserDaoTest.class);   
          @Test   
          public void loggerTest(){     
              logger.info("info:进入了TestLog4j");   
              logger.debug("debug:调试");     
              logger.error("error:错误");   
              logger.fatal("fatal:致命错误");   
          } 
      }
    
    • 导包:import org.apache.log4j.Logger;

    • 获取日志对象,参数为当前类的class

    • 设置日志级别

      7 分页

      为什么要分页?

​ 答:减少数据处理量

7.1 limit分页

  • 语法

             select * from xxx limit startIndex,pageSize 
             select * from user limit 3;
    
  • mybatis的sql语句如果有多个参数,需要用map封装

  • 接口 UserMapper.java

      List<User> selectLimit(Map<String,Integer> map);
    
  • Mapper.xml

      <select id="selectLimit" parameterType="map" resultMap="UserMap">    
      select * from mybatis.user limit #{startIndex},#{pageSize} 
      </select>
    
  • 测试

      public class UserDaoTest {  
          @Test   
          public void limitTest(){  
              SqlSession sqlSession = MybatisUtils.getSqlSession();          UserMapper mapper = sqlSession.getMapper(UserMapper.class);  
              Map<String, Integer> map = new HashMap<String, Integer>();   
              map.put("startIndex",0);          map.put("pageSize",2);  
              List<User> list=mapper.selectLimit(map);  
              for (User u:               list) { 
                  System.out.println(u);    
              }          sqlSession.close();      }  }
    

    7.2 RowBounds分页

    这种方法不推荐使用,因为官方不推荐

  • 接口

      List<User> selectRowBounds();
    
  • 配置

      <select id="selectRowBounds" resultMap="UserMap">   
      select * from mybatis.user 
      </select>
    
  • 测试

      @Test
    public void selectRowBounds(){
          SqlSession sqlSession = MybatisUtils.getSqlSession(); 
          RowBounds rowBounds = new RowBounds(0,2); 
          List<User> list = sqlSession.selectList("com.yu.dao.UserMapper.selectRowBounds",null,rowBounds);   
          for (User user : list) { 
              System.out.println(user); 
          }    
          sqlSession.close(); 
      }
    

7.3 分页插件

MyBatis分页插件 PageHelper

  • 网站:点击访问
  • 网站有详细的文档,此处不在介绍,了解即可,需要用的时候再学

8 使用注解开发

注解的本质是使用反射,底层是代理模式(见设计模式)

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

8.1 注解查找

  • 接口

      @Select("select * from mybatis.user")  
       List<User> selectAll();
    
  • 注册绑定

      <mappers>    
      <mapper class="com.yu.dao.UserMapper"/>
      </mappers>
    
  • 测试

      @Test  public void selectAll(){   
          SqlSession sqlSession = MybatisUtils.getSqlSession();      //底层主要应用反射    
          UserMapper mapper = sqlSession.getMapper(UserMapper.class); 
          List<User> list=mapper.selectAll();  
          for (User user : list) {      
              System.out.println(user);
          }     
          sqlSession.close();
      }
    

8.2 注解CRUD

  • 设置自动提交

      public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
    
  • Mapper

      //多个参数情况下,有两种解决方式,一个map封装,另一种是注解Param
    @Select("select * from mybatis.user where id=#{id}") 
    User selectUserById(@Param("id") int id); 
    @Select("select * from mybatis.user")
    List<User> selectAll(); 
    @Insert("insert into mybatis.user() values(#{id},#{name},#{password}) ")  
    boolean insertUser(User u); 
    @Update("update user set name=#{name},pwd=#{password} where id = #{id}") 
    boolean updateUser(User u); 
    @Delete("delete from mybatis.user where id=#{id}") 
    boolean deleteUser(@Param("id") int id);
    
  • Test

        @Test
        public void getUserById() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userById = mapper.getUserById();
            for (User user : userById) {
                System.out.println(user);
            }
        }
    User{id=1, name='张三', password='null'}
    User{id=2, name='李四', password='null'}
    User{id=3, name='王五', password='null'}
    User{id=4, name='六六六', password='null'}
    User{id=5, name='7777', password='null'}
    User{id=6, name='李六', password='null'}
    
    
    

8.3 @Param注解

  • 基本类型的参数和String类型,需要加上这个注解

  • 引用类型不需要加

  • 如果只有一个基本类型的参数,可以省略

  • 我们在sql中引用的就是@Param(“xxx”)中设定的属性名

  • 如果@parm后面定义的什么,匹配的时候就应该是什么

  • @Select("select * from mybatis01.user where id = #{id2}")
    List<User> getUserByIdandname(@Param("id") int id2);
    

9 MyBatis执行流程

  1. Resource获取加载全局配置文件

  2. 实例化SqlSessionFactoryBuilder构造器

  3. 解析配置文件流XMLConfigBuilder

  4. Configuration所有的配置信息

  5. SqlSessionFactory实例化

  6. transaction事务管理器

  7. 创建executor执行器

  8. 创建sqlSession

  9. 实现CRUD

  10. 查看是否执行成功

  11. 提交事务

  12. 关闭sqlSession连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVYTM5zS-1671198316518)(C:\Users\卢申澳\AppData\Local\Temp\1669628502633.png)]

10 Lombok

Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。

10.1 安装方法

  1. 左上角File->Settings->Plugins

  2. 搜索Lombok,下载安装

  3. 导入maven

     <dependency>     <groupId>org.projectlombok</groupId>     <artifactId>lombok</artifactId>     <version>1.18.10</version> </dependency>
    

10.2 使用Lombok

  • Lombok的支持

      @Getter and @Setter  @FieldNameConstants  @ToString  @EqualsAndHashCode  @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor  @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog  @Data  @Builder  @SuperBuilder  @Singular  @Delegate  @Value  @Accessors  @Wither  @With  @SneakyThrows  @val  @var  experimental @var  @UtilityClass  Lombok config system  Code inspections  Refactoring actions (lombok and delombok)
    
  • 常用支持

      @Data支持: 无参构造,getter&setter,toString,hashCode,equals  @AllArgsConstructor: 有参构造  @NoArgsConstructor: 无参构造
    
  • 使用方法,在实体类上加注解

11 多对一(一对多)的处理

  • 多个学生对应一个老师

  • 学生关联老师,多对一

  • 老师管理集合,一对多

  • Sql建表

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wM7v2jVO-1671198316518)(C:\Users\卢申澳\AppData\Local\Temp\1669628628585.png)]

      create table `teacher`
      (  `id` int not null, 
       `name` varchar(30) default null, 
       primary key(`id`) 
      ) engine=InnoDB default charset=utf8; 
      insert into teacher values (1,'王老师'); 
      create table `student`( 
          `id` int not null, 
          `name` varchar(30) default null,
          `tid` int not null,  primary key(`id`),
          key `FK_tid` (`tid`),  
          constraint `FK_tid` foreign key(`tid`) references `teacher`(`id`)  ) engine=InnoDB default charset=utf8;
    

11.1 测试环境搭建

  1. 导入Lombok
  2. 新建Teacher,Student实体类
  3. 新建Mapper接口
  4. 在resources新建com->xxx->dao文件夹
  5. 新建xxxMapper.xml文件
  6. 在mybatis-config.xml中注册绑定xxxMapper.xml
  7. 在TeacherMapper接口中创建selectAll()方法
  8. 在TeacherMapper.xml中写对应的查询
  9. 新建测试类,在测试类中测试使用

11.2 按照查询嵌套处理

  • 代码演示

      @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student { 
        private int id;   
        private String name;   
        private Teacher teacher; 
    }
    
      @Data 
    @AllArgsConstructor
    @NoArgsConstructor
    public class Teacher {   
        private int id; 
        private String name; 
    }
    
      List<Student> selectAll();
    
        <!--  查询思路:1.查询所有学生  2.根据查询出的学生的tid查询老师,子查询   --> 
    <resultMap id="student_teacher" type="Student"> 
        <!-- property是实体类的属性 column是数据库的字段 -->  
        <result property="id" column="id"/> 
        <result property="name" column="name"/>    
        <!-- 复杂的属性,需要单独处理,对象:association 集合collection  -->     
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>  
    </resultMap> 
    <select id="selectAll" resultMap="student_teacher"> 
        select * from mybatis.student 
    </select>
    <select id="getTeacher" resultType="Teacher">   
            select * from mybatis.teacher where id=#{tid}
    </select>
    
       @Test  public void selectAll(){   
           SqlSession sqlSession = MybatisUtils.getSqlSession();  
           StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);      List<Student> studentList = mapper.selectAll();
           for (Student s:           studentList) {  
               System.out.println(s);
           }      
           sqlSession.close(); 
       }
    

11.3按照结果嵌套处理

  • 代码演示

      List<Student> selectAll2();
    
      <select id="selectAll2" resultMap="S_T">   
          select s.id sid,s.name sname,t.name tname 
          from mybatis.student s,mybatis.teacher t      where s.tid=t.id
    </select> 
    <resultMap id="S_T" type="Student">   
              <result property="id" column="sid"/> 
              <result property="name" column="sname"/>  
              <association property="teacher" javaType="Teacher">    
             <result property="name" column="tname"/>    
              </association> 
    </resultMap>
    
      //测试代码
    

11.4 回顾Mysql多对一查询方式

  • 子查询
  • 联表查询

11.5 一对多的处理

  • 代码演示

      @Data
    @AllArgsConstructor 
    @NoArgsConstructor 
    public class Teacher {    
        private int id;   
        private String name;   
        //老师拥有多个学生  
        private List<Student> students; 
    }
    
      @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
        private int id;  
        private String name;
        private int tid; 
    }
    
      public interface TeacherMapper { 
          List<Teacher> selectAll();  
          //获取指定老师下的所有学生   
          Teacher getTeacher(@Param("tid")int id); 
          Teacher getTeacher2(@Param("tid")int id); 
          List<Student> getStudents(@Param("tid")int id);  }
    

    按照结果进行查询

       <select id="selectAll" resultType="Teacher">  
           select * from mybatis.teacher 
    </select> 
    <select id="getTeacher" resultMap="S_T"> 
        select t.id tid, t.name tname,s.name sname 
        from mybatis.teacher t,mybatis.student s    
        where s.tid=tid and tid=#{tid} 
    </select>
    <resultMap id="S_T" type="Teacher">  
        <result property="id" column="tid"/> 
        <result property="name" column="tname"/>  
        <!-- 集合中的泛型信息,我们使用ofType -->   
        <collection property="students" ofType="Student">  
            <result property="name" column="sname"/>  
            <result property="tid" column="tid"/>    
        </collection> 
    </resultMap> 
    ===============================================================================
    
    

    按照查询嵌套处理

       <select id="getTeacherById3" resultMap="Teacher_Student3">
            select * from mybatis01.teacher where id = #{tid}
        </select>
        <resultMap id="Teacher_Student3" type="Teacher">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <collection property="studentList" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
        </resultMap>
        <select id="getStudent" resultType="Student">
            select * from mybatis01.student where tid = #{id}
        </select>
    

11.6 小结

  • 关联 association 多对一 javatype
  • 集合 collection 一对多 oftype
  • 复杂的属性,需要单独处理,对象:association 集合collection
  • 集合中的泛型信息,我们使用ofType
  • javaType 指定实体类中属性的类型
  • ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点:

  • 保证sql语句的可读性
  • 注意一对多和多对一属性和字段的问题
  • 面试要点
    • Mysql引擎
    • InnoDB底层原理
    • 索引
    • 索引优化

12 动态SQL

什么是动态SQL?

动态SQL是指根据不同的条件生成不同的SQL语句

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

12.1 搭建环境

  • 搭建数据库

      create table blog(  
          id varchar(50) not null comment '博客id',
          title varchar(100) not null comment '博客标题', 
          author varchar(30) not null comment '博客作者', 
          ctreate_time datetime not null comment '创建时间',
          views int not null comment '浏览量' 
      )engine=InnoDB default charset=utf8;
    

创建基础工程

  • 导包

  • 编写配置文件

    mybatis-config 与MybatisUtils

  • 编写实体类日

      import java.util.Date; 
    @Data 
    @AllArgsConstructor
    @NoArgsConstructor 
    public class Blog {  
          private String id;  
          private String title;  
          private String author; 
          private Date createTime; 
          private int views; 
    }
    
  • 编写实体类对应Mapper接口和Mapper.xml文件

      public interface BlogMapper {}
    
      <?xml version="1.0" encoding="UTF-8" ?>  <!DOCTYPE mapper          PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
    <mapper namespace="com.yu.dao.BlogMapper">
    </mapper>
    
  • 解决数据库字段名和实体类属性名不一致的问题

      <!--       有两种解决方法:     
         1.如果不是按规范转换,在xxxMapper.xml用ResultMap,上面已经介绍过      
         2.如果是规范命名,在mybatis-config.xml文件中<settings>-><setting>->id="mapUnderscoreToCamelCase" value="true",它的作用是驼峰命名转换 
         3.直接在查询的列名中起别名-->  
    <settings>   
          <setting name="logImpl" value="STDOUT_LOGGING"/> 
          <setting name="mapUnderscoreToCamelCase" value="true"/>  
    </settings>
    

12.2 if

  • 使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

      <select id="findActiveBlogWithTitleLike"   
              resultType="Blog"> 
          SELECT * FROM BLOG    WHERE state = ‘ACTIVE’  
          <if test="title != null">     
              AND title like #{title}   
          </if> 
    </select>
    
  • 代码演示

      //查询博客
    List<Blog> queryBlogIf(Map<String,Object> map);
    
      <select id="queryBlogIf" parameterType="map" resultType="Blog">  
          select * from mybatis.blog where 1=1  
          <if test="title!=null">     
              and title = #{title}    
          </if>   
          <if test="author!=null"> 
             and author = #{author}   
          </if> 
    </select>
    
    方法二带有where 可以帮你取消and 或不符合的sql
    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
            <!--id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <if test="title !=null">
                    and title = #{title}
                </if>
                <if test="author !=null">
                    and author = #{author}
                </if>
            </where>
        </select>
    
    
    ```java
      @Test
        public void queryBlogByIf() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map map = new HashMap();
            //map.put("title", "Mybatis那么简单");
            map.put("author", "卢哥说");
            List<Blog> blogList = mapper.queryBlogByIf(map);
            for (Blog blog : blogList) {
                System.out.println(blog);
            }
            sqlSession.close();
        }
    

12.3 choose、when、otherwise

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

  • 还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。

  • 代码演示

        List<Blog> queryBlogByChoose(Map map);
    
     <select id="queryBlogByChoose" parameterType="map" resultType="Blog">
            <!--        id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <choose>
                    <when test="title !=null">
                         title = #{title}
                    </when>
                    <when test="author !=null">
                        and author = #{author}
                    </when>
                    <otherwise>
                        and views = #{views}
                    </otherwise>
                </choose>
            </where>
        </select>
    
        
        @Test
        public void queryBlogByChoose() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map map = new HashMap();
            map.put("title", "Mybatis那么简单");
            map.put("author", "卢哥说");
            map.put("views", 999);
            List<Blog> blogList = mapper.queryBlogByChoose(map);
            for (Blog blog : blogList) {
                System.out.println(blog);
            }
    
            sqlSession.close();
        }
    他只符合你的第一条,如果第一条满足不会往下进行否则反之
    

12.4 trim、where、set

  • 前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题

  • where

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 就是只有下面if成立才启动where 且可以帮你取出and 或 or

  • 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

  •   <trim prefix="WHERE" prefixOverrides="AND |OR ">    ...  </trim>
    
    
        List<Blog> queryBlogByIf(Map map);
        
            
            <select id="queryBlogByIf" parameterType="map" resultType="Blog">
            <!--        id,title,author,ctreate_time as createTime,views-->
            select * from mybatis01.blog
            <where>
                <if test="title !=null">
                    and title = #{title}
                </if>
                <if test="author !=null">
                    and author = #{author}
                </if>
            </where>
        </select>
    
    
    

  • set

  • 这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

  • prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容

  • 用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

  • 来看看与 set 元素等价的自定义 trim 元素吧:

  •  <trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
     prefix:在trim标签内 sql语句加上前缀。 就是在条件前加上
     suffix:在trim标签内 sql语句加上后缀。 就是在条件结束加上
     suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。
     prefixOverrides:指定去除多余的前缀内容
    
  •   
     int updateBlog(Map map);
       <update id="updateBlog" parameterType="map">
            update mybatis01.blog
            <set>
                <if test="title !=null">
                    title = #{title},
                </if>
                <if test="author !=null">
                    author = #{author}
                </if>
            </set>
            where id = #{id}
        </update>
       </select>
    

12.5 SQL片段

  • 所谓动态sql,本质还是SQL语句,只是我们可以在SQL层面,执行逻辑代码

  • 有的时候,我们可以将一些功能的部分抽取出来(类似函数的封装),方便复用

     1.使用SQL标签抽取公共部分
    
    title = #{title} and author = #{author} ```

    2.在需要使用的地方使用Include标签引用即可

  • 使用sql标签封装,在需要的地方使用include标签引入

    <select id="queryBlogIf" parameterType="map" resultType="Blog">  
        select * from mybatis.blog   
        <where>  
            <include refid="if-title-author">
            </include> 
            <!--      <if test="title!=null">          title = #{title}      </if>      <if test="author!=null">          and author = #{author}      </if>      -->      </where> 
    </select>
    
  • 注意事项:

    • 最好基于单表定义SQL片段
    • 不要存在where标签

12.6 foreach

  • 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

      <select id="selectPostIn" resultType="domain.blog.Post">  
          SELECT *    FROM POST P    WHERE ID in   
          <foreach item="item" index="index" collection="list"  
                  open="(" separator="," close=")">  
              #{item}  
          </foreach>  </select>
    
  • foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

  • 提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

  • 至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

  • foreach的作用,就是为了替代下面这种复杂的语句

    <select id="queryBlogByForeach" parameterType="map" resultType="Blog">
            select * from mybatis01.blog
            <where>
                <!--elect * from mybatis01.blog where id  = IN (1 OR 2 OR 3)-->
                <foreach collection="ids" item="id" open=" in (" close=")" separator="or">
                    id = #{id}
                </foreach>
            </where>
        </select>
    其中item 后的参数就相当于 sql where后面得数据线
    
  • 代码演示(改一下数据库id)

      List<Blog> queryBlogForeach(Map<String,Object> map);
    
      <select id="queryBlogForeach" parameterType="map" resultType="Blog">      select * from mybatis.blog      <where>          <foreach collection="ids" item="id" open="(" close=")" separator="or">              id=#{id}          </foreach>      </where>  </select>
    
       @Test  public void queryBlogForeach(){      SqlSession sqlSession = MybatisUtils.getSqlSession();      BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);      Map<String,Object> map=new HashMap<String,Object>();      List<Integer> ids = new ArrayList<Integer>();//        ids.add(1);      ids.add(2);      ids.add(3);      map.put("ids",(List)ids);      List<Blog> blogs = mapper.queryBlogForeach(map);      sqlSession.close();  }
    

    动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式去排列组合

    建议:

    1. 现在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可

13 缓存

13.1 简介

  • 为什么要使用缓存

    每次查询都要连接数据库,比较耗资源,我们把查询到的数据暂存到内存里面,下次查询的时候,从内存读取, 这个地方就叫缓存。

  • 什么样的数据适用于缓存?

    经常查询且不经常改变的数据

13.2 Mybatis缓存

  • Mybatis系统默认定义了两级缓存
    • 默认情况下,只有一级缓存开启(SqlSession缓存,也称为本地缓存)
    • 二级缓存需要手动配置,它是基于namespace级别的缓存
    • Mybatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存

13.3 一级缓存

  • 测试步骤

    1. 开启日志

    2. 测试在一个Session中查询两次的计量局

    3. 查看日志输出

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zCE9tsq-1671198316519)(C:\Users\卢申澳\AppData\Local\Temp\1669888868331.png)]

      sql查询出来一次

  • 代码演示

        User getUserById(@Param("id") int id);
    
     <select id="getUserById" parameterType="int" resultType="User">
            select * from mybatis01.user where id = #{id}
        </select>
    
        @Test
        public void userTest() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User userById = mapper.getUserById(1);
            System.out.println(userById);
            System.out.println("===========================");
    
            User userById2 = mapper.getUserById(1);
            System.out.println(userById);
    
            System.out.println(userById == userById2);
            sqlSession.close();
        }
    

  • 缓存失效的情况

    1. 查询不同的xxxMapper.xml

    2. 增删改

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQlGrCCi-1671198316519)(C:\Users\卢申澳\AppData\Local\Temp\1669890361073.png)]

    3. 查询不同的东西

    4. 手动清理缓存(sqlSession.clearCache())

      @Test
      public void updateUser() {
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          User userById = mapper.getUserById(1);
          System.out.println(userById);
      
          System.out.println("===========================");
          mapper.updateUser(new User(2, "张四111", "123456789"));
              sqlSession.clearCache();
          System.out.println("===========================");
          User userById2 = mapper.getUserById(1);
          System.out.println(userById2);
      
          System.out.println(userById == userById2);
          sqlSession.close();
      }
      

​ 小结:一级缓存默认是开启的,只在一次sqlsession中有效 ,也就是拿到链接关闭链接

一级缓存就是map

13.4 二级缓存

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

  • 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

      <cache/>
    
    
         <!--显示的开启全局缓存-->
            <setting name="cacheEnabled" value="true"/>
    
  • 基本上就是这样。这个简单语句的效果如下:

  • 这些属性可以通过 cache 元素的属性来修改。比如:

      <cache  
             eviction="FIFO"   
             flushInterval="60000"   
             size="512" 
             readOnly="true"/>
    
  • 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

  • 使用二级缓存步骤:

    1. 开启全局缓存

      <!-- 虽然默认开启,但是写上可以让看代码的人明白 -->
      <setting name="cacheEnabled" value="true"/>
      
    2. 在要使用二级缓存的Mapper.xml中,写标签

      <cache 
             eviction="FIFO" 
             flushInterval="60000"
             size="512"
             readOnly="true"/>
      
    3. 测试使用

        @Test
          public void updateUser2() {
              SqlSession sqlSession = MybatisUtils.getSqlSession();
              SqlSession sqlSession2 = MybatisUtils.getSqlSession();
      
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              User userById = mapper.getUserById(1);
              System.out.println(userById);
              sqlSession.close();
      
              System.out.println("===========================");
              UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
              User userById2 = mapper2.getUserById(1);
              System.out.println(userById2);
      
          System.out.println(userById == userById2);
          sqlSession2.close();
      }
      
      
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LAepwSaJ-1671198316520)(C:\Users\卢申澳\AppData\Local\Temp\1669892021179.png)]
      
      
  • 问题

    • 我们需要实体类序列化,否则会抛出异常
  • 小结

    • 二级缓存在同一个Mapper下有效
    • 所有的数据都会先放在一级缓存中
    • 当会话提交或者关闭,数据会被转存到二级缓存中

13.5 缓存原理

  • 图片演示
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mf1tHHfg-1671198316520)(C:\Users\卢申澳\AppData\Local\Temp\1670479127194.png)]

缓存顺序

1.先看二级缓存中有没有

2.再看一级缓存中有没有

3.查询数据库

13.6 自定义缓存ehcache

  • 简介

    EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

  • 使用

    1. 导包

      <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --><dependency>
          <groupId>org.mybatis.caches</groupId> 
          <artifactId>mybatis-ehcache</artifactId>
          <version>1.2.2</version>
      </dependency>
      
    2. 写入配置文件(resources->ehcache.xml)

      <?xml version="1.0" encoding="UTF-8"?>
      <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"><!-- 磁盘缓存位置 -->
          <diskStore path="java.io.tmpdir/ehcache"/><!-- 默认缓存 -->
          <defaultCache
                  maxEntriesLocalHeap="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  maxEntriesLocalDisk="10000000"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU">
              <persistence strategy="localTempSwap"/>
          </defaultCache><!-- helloworld缓存 -->
          <cache name="HelloWorldCache"
                 maxElementsInMemory="1000"
                 eternal="false"
                 timeToIdleSeconds="5"
                 timeToLiveSeconds="5"
                 overflowToDisk="false"
                 memoryStoreEvictionPolicy="LRU"/>
      </ehcache>
      
    3. 在Mapper中指定

      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
      
    4. 测试使用(用之前的代码即可)

  • 自定义缓存

    只要实现了org.apache.ibatis.cache.Cache接口,就能定义自己的缓存,但是实现比较复杂,只需要会使用就行,ehcache是继承了AbstractEhcacheCache,该类已经实现了Cache接口。

      public class MyCache implements Cache {      @Override      public String getId() {          return null;      }      @Override      public void putObject(Object key, Object value) {      }      @Override      public Object getObject(Object key) {          return null;      }      @Override      public Object removeObject(Object key) {          return null;      }      @Override      public void clear() {      }      @Override      public int getSize() {          return 0;      }  }
    
  • 实际开发中使用的缓存

    • 在实际开发中,我们更多的使用Redis来做缓存
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值