zoukankan      html  css  js  c++  java
  • Mybatis自学之路(狂神说)

    MyBatis自学之路

    1 Mybatis简介

    1.1 mybatis是什么?

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

    1.2 myBatis优点

    • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
    • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
    • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql

    1.3 持久化

    数据持久化

    • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
    • 内存:断电即失
    • 数据库(Jdbc),io文件持久化。

    1.4 持久层

    Dao层、Service层、Controller层

    • 完成持久化工作的代码块
    • 层界限十分明显

    技术没有高低之分,只有使用技术的人有高低之分

    2 Mybatis第一个程序

    2.1 环境搭建

    1. 准备数据库

      create database mybatis;
      use mybatis;
      create table user(
          id int(10) primary key,
          `name` varchar(30),
          `password` varchar(30)
      )engine=INNODB default charset = utf8;
      insert into user values
                              (1,'zhangsan','123'),
                              (2,'lisi','123'),
                              (3,'wangwu','123');
      
    2. 添加环境依赖并创建子模块

          <dependencies>
              <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
              <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis</artifactId>
                  <version>3.4.6</version>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.13</version>
                  <scope>test</scope>
              </dependency>
          </dependencies>
      
    3. 从 XML 中构建 SqlSessionFactory

      • 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

      • 创建mybatis-configuration.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="com.mysql.jdbc.Driver"/>
                        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;characterEncoding=utf-8&amp;useUnicode=true"/>
                        <property name="username" value="root"/>
                        <property name="password" value="123456"/>
                    </dataSource>
                </environment>
            </environments>
            <mappers>
                <mapper resource="org/mybatis/example/BlogMapper.xml"/>
            </mappers>
        </configuration>
        
      • 创建MybatisUtil工具类

        public class MybatisUtil {
            //创建SqlSessionFactory实例
            private static  SqlSessionFactory sqlSessionFactory = null;
            static {
                String resource = "mybatis-configuration.xml";
                SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
                try {
                    sqlSessionFactory = factoryBuilder.build(Resources.getResourceAsStream(resource));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例.
            // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
            public static SqlSession getSqlSession(){
                return sqlSessionFactory.openSession();
            }
        }
        

    2.2 编写查询语句代码

    1. 准备实体类User

    2. 编写dao接口

      public interface UserDao {
          List<User> getUserList();
      }
      
    3. 编写dao接口对应的mapper.xml配置文件

      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <!--namespace 对应的 dao接口 id 方法名, 返回值的类型:填写list中传入的泛型-->
      <mapper namespace="com.iandf.dao.UserDao">
          <select id="getUserList" resultType="com.iandf.pojo.User">
              select *
              from mybatis.user
          </select>
      </mapper>
      
    4. 添加pom.xml添加配置,防止java目录下的资源配置文件导出失败

          <build>
              <resources>
                  <resource>
                      <directory>src/main/java</directory>
                      <includes>
                          <include>**/*.xml</include>
                          <include>**/*.properties</include>
                      </includes>
                      <filtering>true</filtering>
                  </resource>
              </resources>
          </build>
      
    5. 使用junit测试

          @org.junit.Test
          public void Test(){
              SqlSession sqlSession = MybatisUtil.getSqlSession();
              UserDao mapper = sqlSession.getMapper(UserDao.class);
              List<User> userList = mapper.getUserList();
              for (User user : userList) {
                  System.out.println(user.toString());
              }
          }
      

    2.3 遇到的错误

      1. 类找不到,在终端执行 mvn idea:idea指令之后就能解决
      2. 配置文件mybatis-configuration.xml找不到,没有配置mapper 或者 resources文件夹没有被标记为资源文件夹
    

    2.4 CRUD

    1. UserMapper接口类

      public interface UserMapper {
          List<User> getUserList();
          //insert
          int addUser(User user);
          //delete
          int deleteUserById(int id);
          //update
          int updateUserById(User user);
          //select
          User queryUserById(int id);
      }
      
    2. mapper.xml配置文件

      <mapper namespace="com.iandf.dao.UserMapper">
          <select id="getUserList" resultType="com.iandf.pojo.User">
              select *
              from mybatis.user
          </select>
      
          <insert id="addUser"  parameterType="com.iandf.pojo.User">
             #  #{user.id}也可以省去对象名称
             insert into mybatis.user(id, name, password) VALUES (#{user.id},#{name},#{password})
          </insert>
      
          <delete id="deleteUserById"  parameterType="int">
              delete from mybatis.user where id = #{id}
          </delete>
      
          <update id="updateUserById" parameterType="com.iandf.pojo.User">
              update mybatis.user set name = #{name},password = #{password} where id = #{id}
          </update>
      
          <select id="queryUserById" parameterType="int" resultType="com.iandf.pojo.User">
              select * from mybatis.user where id = #{id}
          </select>
      </mapper>
      
    3. 测试文件

          @Test
          public void deleteUserByIdTest(){
              SqlSession sqlSession = MybatisUtil.getSqlSession();
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              int nums = mapper.deleteUserById(4);
              System.out.println(nums);
              sqlSession.commit();
          }
          
          @Test
          public void queryUserByIdTest(){
              try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                  User user = mapper.queryUserById(4);
                  System.out.println(user.toString());
              }
          }
      

    note:

      1. 增删改需要提交事务
      2. sqlSession相当于connection mapper相当于statement
      3. #{xxx} xxx必须与编写的字段和参数名相同 
    

    2.5 使用万能的map传递参数

    使用map插入一条记录

    1. dao接口

          int addUserByMap(Map<String,Object> map);
      
    2. mapper.xml

          <insert id="addUserByMap"  parameterType="map">
              insert into mybatis.user(id, name, password) VALUES (#{userID},#{userName},#{UserPassword})
          </insert>
      
    3. 测试案例

          @Test
          public void addUserTestByMap(){
              try(SqlSession sqlSession = MybatisUtil.getSqlSession()){
                  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                  HashMap<String, Object> map = new HashMap<>();
                  map.put("userID",5);
                  map.put("userName","王五");
                  map.put("UserPassword","123456");
                  mapper.addUserByMap(map);
                  sqlSession.commit();
              }
          }
      

    NOTE:

    1. ​ 传递多个参数可以使用对象,也可以使用map
    2. ​ 传递一个参数,直接使用方法形参即可

    2.6 模糊查询

    1. 在传递参数时使用通配符,在sql语句中写死

      1. mapper.xml

            <select id="queryUserByLikeName" resultType="com.iandf.pojo.User">
                select * from mybatis.user where name like #{value}
            </select>
        
      2. dao接口和测试代码

            //select
            List<User> queryUserByLikeName(String value);
            @Test
            public void queryUserByLikeNameTest(){
                try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                    List<User> users = mapper.queryUserByLikeName("%n%");
                    for (User user : users) {
                        System.out.println(user.toString());
                    }
                }
            }
        
    2. 在sql语句中进行拼接,传递参数时只需要传用户输入部分,这样sql在进行拼接,容易导致sql注入

      1. mapper.xml

            <select id="queryUserByLikeName2" parameterType="String" resultType="com.iandf.pojo.User">
                select * from mybatis.user where name like "%"#{value}"%"
            </select>
        
      2. dao接口和测试代码

            List<User> queryUserByLikeName2(String value);
                @Test
            public void queryUserByLikeName2Test(){
                try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                    List<User> users = mapper.queryUserByLikeName2("n");
                    for (User user : users) {
                        System.out.println(user.toString());
                    }
                }
            }
        

    3 配置解析

    3.1 核心配置文件

    Mybatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息。

    configuration(配置)
        properties(属性)
        settings(设置)
        typeAliases(类型别名)
        typeHandlers(类型处理器)
        objectFactory(对象工厂)
        plugins(插件)
        environments(环境配置)
        	environment(环境变量)
        		transactionManager(事务管理器)
        		dataSource(数据源)
        databaseIdProvider(数据库厂商标识)
        mappers(映射器)
    

    3.2 环境配置 environments

    MyBatis 可以配置成适应多种环境

    不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境

    MyBatis默认的事务管理器就是JDBC ,连接池:POOLED

    3.3 属性 properties

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

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

    1. 编写一个配置文件

      db.properties

      driver=com.mysql.cj.jdbc.Driver
      url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
      username=root
      password=root
      1234
      
    2. 在核心配置文件中引入

      <!--引用外部配置文件-->
      <properties resource="db.properties">
          <property name="username" value="root"/>
          <property name="password" value="root"/>
      </properties>
      12345
      
      • 可以直接引入外部文件
      • 可以在其中增加一些属性配置
      • 如果两个文件有同一个字段,优先使用外部配置文件的

    3.4 类型别名 typeAliases

    • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置.
    • 意在降低冗余的全限定类名书写。
    <!--可以给实体类起别名-->
    <typeAliases>
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
    </typeAliases>
    

    也可以指定一个包,每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author,;若有注解,则别名为其注解值。见下面的例子:

    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>
    

    在实体类比较少的时候,使用第一种方式。如果实体类十分多,建议用第二种扫描包的方式。

    第一种可以DIY别名,第二种不行,如果非要改,需要在实体上增加注解。

    @Alias("author")
    public class Author {
        ...
    }
    

    3.5 映射器 mappers

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

    方式一:【推荐使用】

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

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

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

    方式三:使用包扫描进行注入

    <mappers>
        <package name="com.kuang.dao"/>
    </mappers>
    

    方法二和方法三的注意点:

    • 接口和他的Mapper配置文件必须同名
    • 接口和他的Mapper配置文件必须在同一个包下

    3.6 作用域和生命周期

    声明周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题

    SqlSessionFactoryBuilder:

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

    SqlSessionFactory:

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

    SqlSession:

    • 连接到连接池的一个请求
    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
    • 用完之后需要赶紧关闭,否则资源被占用!

    image-20200817211415437

    4、解决属性名和字段名不一致的问题

    1. 问题

    数据库中的字段

    image-20200817211941452

    实体类字段

    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
    <select id="queryUserById" parameterType="int" resultType="user">
        select * from mybatis.user where id = #{user.id}
    </select>
    

    上面这个类有 3 个属性:id,name 和 pwd。这些属性会对应到 select 语句中的列名。这些属性会对应到 select 语句中的列名。但是类的名字和数据库的字段要一致。这样的一个 JavaBean 可以被映射到 ResultSet

    上面案例中,pwd和password不同所以测试结果显示pwd为null

    image-20200817212945887

    解决方法:

    • 起别名

      <select id="getUserById" resultType="com.kuang.pojo.User">
          select id,name,password as pwd from USER where id = #{id}
      </select>
      

      MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配

    2. resultMap

    结果集映射

    <resultMap id="resultMap" type="user">
        <result column="password" property="pwd"/>
    </resultMap>
    <select id="queryUserById" parameterType="int" resultMap="resultMap">
        #也可以省去对象名称
        select * from mybatis.user where id = #{user.id}
    </select>
    
    • resultMap 元素是 MyBatis 中最重要最强大的元素。
    • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
    • ResultMap 的优秀之处——你完全可以不用显式地配置它们。

    5. 日志

    image-20200818165301459

    掌握 stdout_logging,log4j

    5.1 STDOUT_LOGGING

    在mybatis_config.xml文件中添加设置

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

    测试结果

    image-20200818170709958

    5.2 LOG4J

    简介

    Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    代码测试

    1. 添加设置

      <settings>
          <setting name="logImpl" value="LOG4J"/>
      </settings>
      
    2. 导入log4j的依赖包

      <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
      </dependency>
      
    3. 编写log4j.properties

      #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
      log4j.rootLogger=DEBUG,console,file
      
      #控制台输出的相关设置
      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
      #文件输出的相关设置
      log4j.appender.file = org.apache.log4j.RollingFileAppender
      log4j.appender.file.File=./log/rzp.log
      log4j.appender.file.MaxFileSize=10mb
      log4j.appender.file.Threshold=DEBUG
      log4j.appender.file.layout=org.apache.log4j.PatternLayout
      log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
      #日志输出级别
      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.sq1.PreparedStatement=DEBUG
      
    4. 测试结果

      image-20200818172259075

    5. 简单使用

      Logger logger = Logger.getLogger(UserDaoTest.class);
      logger.debug("debug");
      logger.error("error");
      logger.info("info");
      

    6. Mybatis的执行流程

    1. 创建SqlSessionFactoryBuilder

    2. 使用建造者模式构建SqlSessionFactory

      1. 源码解析,使用配置文件的输入流构建一个XMLConfigBuilder对象,使用XMLConfigBuilder为参数构建DefaultSqlSessionFactory

        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
                SqlSessionFactory var5;
                try {
                   
                    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                    var5 = this.build(parser.parse());
                } catch (Exception var14) {
                    ....
                }
                return var5;
        }
        
        public SqlSessionFactory build(Configuration config) {
                return new DefaultSqlSessionFactory(config);
        }
        
      2. 执行结果

        image-20200819194118360

      3. 使用SqlSessionFactory创建sqlSession对象

        1. 源码分析,使用使用transaction作为参数生成了executor,configuration,executor生成了sqlSession

          private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
                  Transaction tx = null;
                  DefaultSqlSession var8;
                  try {
                      Environment environment = this.configuration.getEnvironment();
                      TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                      Executor executor = this.configuration.newExecutor(tx, execType);
                      var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
                  } catch (Exception var12) {
          			...
                  } 
                  return var8;
              }
          
        2. 执行结果

      4. 实现CRUD

    NOTE:

    1. mybatis执行流程使用了大量的反射,例如它使用反射知道我们所返回的类型是列表
    2. 使用了建造者和工厂设计模式

    流程图如下:

    Temp

    7. 使用注解执行CRUD

    1. mapper接口

      1. //login
        @Select("select * from mybatis.user where name=#{name} and password = #{pwd}")
        User login(@Param("name") String name, @Param("pwd") String password);
        
    2. 使用class=xxx编写mybatis_configuration.xml文件

          <mappers>
              <!--<mapper resource="com/iandf/dao/UserDaoMapper.xml"/>-->
              <mapper class="com.iandf.dao.UserMapper"/>
          </mappers>
      
    3. 测试

    关于@param("xxx")注解:

    1. 基本类型和String都要加上,引用类型没必要加
    2. 在sql引用上使用的就是param的属性名
    3. 基本类型只有一个的时候可以加也可以不加,建议都加上

    8. 负责查询语句

    8.1 多对一连表查询

    查询语句 (查询学生和学生对应的老师的信息)

    select s.id, s.name ,t.name from student s,teacher t where s.tid = t.id;
    

    程序实现

    1.按照查询嵌套实现

    1. mapper接口

    2. mapper.xml

      <select id="getStudentList" resultMap="studentMap">
          select * from mybatis.student
      </select>
      <resultMap id="studentMap" type="com.iandf.pojo.Student">
          <association property="teacher" column="tid" javaType="com.iandf.pojo.Teacher" select="getTeacher"/>
      </resultMap>
      <select id="getTeacher" resultType="teacher">
          ##{id}中的名字可以随便取,他的值就是列名为column="tid"的值
          select * from mybatis.teacher where id = #{id}
      </select>
      
    3. 测试代码

    1. 按照结果嵌套的方式实现
    1. mapper接口

    2. mapper.xml

          <select id="getStudentList2" resultMap="studentMap2">
              select s.id sId, s.name sName, t.name tName, t.id tId from mybatis.student s, mybatis.teacher t where s.tid = t.id;
          </select>
          <resultMap id="studentMap2" type="student">
              <result property="id" column="sID"/>
              <result property="name" column="sName"/>
              <association property="teacher" javaType="Teacher">
                  <result property="id" column="tID"/>
                  <result property="name" column="tName"/>
              </association>
          </resultMap>
      
    3. 测试代码

    8.2 一对多连表查询

    查询老师和老师交的所有学生

    select t.id tId ,t.name tName,s.id sId, s.name sName from mybatis.student s,mybatis.teacher t where s.tid = t.id and t.id = 1;
    

    按照结果嵌套方式实现

    1. mapper接口

    2. mapper.xml

      <select id="getTeacherInfoById" resultMap="teacherMap" parameterType="int">
         select t.id tId ,t.name tName,s.id sId, s.name sName from mybatis.student s,mybatis.teacher t where s.tid = t.id and t.id = #{id};
      </select>
      <resultMap id="teacherMap" type="teacher">
          <result column="tId" property="id"/>
          <result column="tName" property="name"/>
          <collection property="students" ofType="student">
              <result property="id" column="sId"/>
              <result property="name" column="sName"/>
          </collection>
      </resultMap>
      
    3. 测试代码

    按照查询嵌套方式实现

    mapper.xml

        <select id="getTeacherInfoById2" resultMap="teacherMap2" parameterType="int">
            select * from mybatis.teacher
        </select>
        <resultMap id="teacherMap2" type="teacher">
            <collection property="students" column="id" ofType="Student" select="getStudents"/>
        </resultMap>
        <select id="getStudents" resultType="student">
            select * from mybatis.student where tid = #{id}
        </select>
    

    8.3 总结

    多对一使用association

    association – 一个复杂类型的关联;许多结果将包装成这种类型

    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用

    一对多使用collection

    collection – 一个复杂类型的集合

    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
    • ofType代表集合的泛型 javaType指的是实体类中的类型

    9. 动态SQL

    9.1 简介

    动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

    使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

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

    9.2 IF

    需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

    1. mapper接口

      List<Blog> queryBlogByIf(Map<String,String> map);
      
    2. mapper.xml

      <select id="queryBlogByIf" 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>
      
    3. 测试

    1=1在项目中是不允许这样使用的,mybatis为我们提供了where标签来解决and或者or加不加的问题

    改进后:

    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
        select * from mybatis.blog
        <where>
            <if test="title != null">
                and title = #{title}
            </if>
            <if test="author != null">
                and author = #{author}
            </if>
        </where>
    </select>
    

    这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

    9.3 set

    同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

    需求:修改博客的title或者author

    1. mapper接口

           void updateBlogBySet(Map<String,String> map);
      
    2. mapper.xml

          <update id="updateBlogBySet" parameterType="map">
              update mybatis.blog
              <set>
                  <if test="title != null">
                      title = #{title},
                  </if>
                  <if test="author != null">
                      author = #{author}
                  </if>
              </set>
              where id = #{id}
          </update>
      
    3. 测试

    9.4 choose、when、otherwise

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

    需求:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回所有的 BLOG

    1. mapper接口

      List<Blog> queryBlogByChoose(Map<String,String> map);
      
    2. mapper.xml

      <select id="queryBlogByChoose" parameterType="map" resultType="blog">
          select * from mybatis.blog
          <where>
              <choose>
                  <when test="title != null">
                      title = #{title}
                  </when>
                  <when test="author != null">
                      and author = #{author}
                  </when>
              </choose>
          </where>
      </select>
      
    3. 测试

    9.5 sql片段

    有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

    提取sql片段

    <sql id="if-title-author">
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </sql>
    

    引用sql片段

    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
        select * from mybatis.blog
        <where>
            <include refid="if-title-author"/>
            <!-- 在这里还可以引用其他的 sql 片段 -->
        </where>
    </select>
    

    NOTE:

    1. 最好是基于单表查询,可以提高片段的可重用性
    2. sql片段中不要包含where,最好只有几个简单的if

    9.6 foreach

    需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

    sql语句

    select * from blog where id in (1,2,3);
    

    代码实现

    1. mapper接口

      List<Blog> queryBlogByForeach(Map<String,Object> map);
      
    2. mapper.xml

      <select id="queryBlogByForeach" resultType="blog" parameterType="map">
          select * from mybatis.blog
          <where>
              <foreach collection="ids" item="id" open="id in (" close=")" separator=",">
                  #{id}
              </foreach>
          </where>
      </select>
      
    3. 测试

      public void queryBlogByForeach(){
          try(SqlSession sqlSession = MybatisUtil.getSqlSession()){
              BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
              HashMap<String, Object> map = new HashMap<>();
              List<Integer> ids = new ArrayList<>();
              ids.add(1);
              ids.add(2);
              ids.add(3);
              map.put("ids",ids);
              List<Blog> blogs = mapper.queryBlogByForeach(map);
              for (Blog blog : blogs) {
                  System.out.println(blog.toString());
              }
          }
      }
      

    10. Mybatis缓存

    10.1 简介

    查询 : 连接数据库 ,耗资源!
    一次查询的结果,给他暂存在一个可以直接取到的地方!--> 内存 : 缓存

    我们再次查询相同数据的时候,直接走缓存,就不用走数据库了

    1. 什么是缓存 [ Cache ]?

      • 存在内存中的临时数据。
      • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
    2. 为什么使用缓存?

      • 减少和数据库的交互次数,减少系统开销,提高系统效率。
    3. 什么样的数据能使用缓存?

      • 经常查询并且不经常改变的数据。【可以使用缓存】

    10.2、Mybatis缓存

    • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
    • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
      • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
      • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
      • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

    10.3、一级缓存

    • 一级缓存也叫本地缓存: SqlSession
      • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
      • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

    测试步骤:

    1. 开启日志!
    2. 测试在一个Sesion中查询两次相同记录
    3. 查看日志输出

    1569983650437

    缓存失效的情况:

    1. 查询不同的东西

    2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

      1569983952321

    3. 查询不同的Mapper.xml

    4. 手动清理缓存!

      sqlsession.clearCache();
      

    小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

    一级缓存就是一个Map。

    10.4、二级缓存

    • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
    • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
    • 工作机制
      • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
      • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
      • 新的会话查询信息,就可以从二级缓存中获取内容;
      • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

    步骤:

    1. 开启全局缓存

      <!--显示的开启全局缓存-->
      <setting name="cacheEnabled" value="true"/>
      
    2. 在要使用二级缓存的Mapper中开启

      <!--在当前Mapper.xml中使用二级缓存-->
      <cache/>
      

      也可以自定义参数

      <!--在当前Mapper.xml中使用二级缓存-->
      <cache  eviction="FIFO"
             flushInterval="60000"
             size="512"
             readOnly="true"/>
      
    3. 测试

      1. 问题:我们需要将实体类序列化!否则就会报错!

        Caused by: java.io.NotSerializableException: com.kuang.pojo.User
        

    小结:

    • 只要开启了二级缓存,在同一个Mapper下就有效
    • 所有的数据都会先放在一级缓存中;
    • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

    10.5、缓存原理

    1569985541106

  • 相关阅读:
    SpringMVC学习指南【笔记6】JSTL标签、函数
    SpringMVC学习指南【笔记5】EL表达式、实现免脚本JSP页面、禁用EL计算的设置
    SpringMVC学习指南【笔记4】数据绑定、表单标签库、转换器、格式化、验证器
    序列封包和序列解包
    python 字符串分割,连接方法
    Jmeter常用插件(转)
    不同的content-type,Jmeter入参不同
    性能监测(CPU)
    正则表达式
    乱码问题
  • 原文地址:https://www.cnblogs.com/iandf/p/13539956.html
Copyright © 2011-2022 走看看