Mybatis学习笔记

1、入门

  1. 搭建环境(Maven)

  2. 导入依赖(mybatismysql-connector-java

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
      
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
    
  3. 配置mybatis(mybatis-config.xml)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
     <!--环境,可以配置多个环境,这里设置了默认的环境为development-->
     <environments default="development">
         <!--第一个环境时id为development的环境-->
         <environment id="development">
             <!--事务管理是JDBC-->
             <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;useUnicode=true&amp;characterEncoding=UTF-8"/>
                 <property name="username" value="root"/>
                 <property name="password" value="123456w"/>
             </dataSource>
         </environment>
     </environments>
    
     <!--每一Mapper.XML都需要在Mybatis核心配置文件中注册-->
     <mappers>
         <mapper resource="com/th/dao/UserMapper.xml"/>
     </mappers>
    
    </configuration>
    
  4. 构建工具类,获取SqlSessionFactory和SqlSession

    public class MybatisUtils {
    
     // 从 XML 中构建 SqlSessionFactory[也可以通过Java 代码构建SqlSessionFactory]
     private static SqlSessionFactory sqlSessionFactory;
     static {
         //第一步:获取SqlSessionFactory对象
         try {
             String resource = "mybatis-config.xml";
             InputStream inputStream = Resources.getResourceAsStream(resource);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
     /*既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在
     数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。*/
    
    //第二步:通过SqlSessionFactory获取SqlSession对象
     public static SqlSession getSqlSession(){
         //通过静态代码块中的sqlSessionFactory对象调用openSession()方法得到一个SqlSession 对象
         //将这个SqlSession 对象返回出去,作为工具提供给需要实现数据库操作的对象使用。
         return sqlSessionFactory.openSession();
     }
    }
    
  5. 编写实体类(可使用lombok

    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;
        }
        ……
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  6. Dao / Mapper接口

    public interface UserMapper {
        List<User> getUserList();
    
  7. 接口实现类Impl【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=绑定一个对应的Dao/Mapper接口,XXXMapper.xml相当于UserDaoImpl,实现Dao接口-->
    <mapper namespace="com.th.dao.UserMapper">
        <!--select查询语句-->
        <!--id为Dao/Mapper接口的一个方法,resultType为返回的一个类型-->
        <select id="getUserList" resultType="com.th.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    
  8. 在Mybatis核心配置文件中注册每一个Mapper.xml

  9. 测试

    public void testGetUserList() {
         //第一步:通过工具类获得SqlSession对象
         SqlSession sqlSession = MybatisUtils.getSqlSession();
    
         //第二步:获取结果集
         try {
             //方式一:getMapper(推荐使用)
             //不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
             UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
             List<User> userList = userMapper.getUserList();
    
             //方式二:
             //List<User> userList = sqlSession.selectList("com.th.dao.UserDao.getUserList");
    
             for (User user : userList) {
                 System.out.println(user);
             }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             //第三步:关闭SqlSession
             sqlSession.close();
         }
     }
    
  10. 注意点

    1. 报错org.apache.ibatis.binding.BindingException: Type interface com.th.dao.UserMapper is not known to the MapperRegistry.

    每一个XXXMapper.xml文件都必须在mybatis.xml文件中进行注册。

    <mappers>
      <mapper resource="com/th/dao/UserMapper.xml"/>
    </mappers>
    
    1. 报错Caused by: java.io.IOException: Could not find resource com/th/dao/UserMapper.xml

    Maven约定大于配置,出现这个错误的原因是Maven默认导出resources下的配置文件,而src目录下的不会被导出,解决方案是配置使其导出src下的配置文件。

    <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>
    

2、三大对象作用域

  • SqlSessionFactoryBuilder
    SqlSessionFactoryBuilder用于创建SqlSessionFactory对象,一旦创建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量
  • SqlSessionFactory
    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式
  • SqlSession
    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例是线程不安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

3、CRUD

  • id: 对应接口中定义的方法名

  • resultType: SQL执行后的返回值

  • parameterType:参数类型

  • 注意:增删改需要提交事务

  • 1. select

    1. 编写接口
    User getUserById(int id);
    
    1. 编写对应的mapper中的sql语句
    <select id="getUserById" parameterType="int" resultType="com.th.pojo.User">
     select * from mybatis.user where id=#{id}
    </select>
    
    1. 测试/实现
    @Test
    public void testGetUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        System.out.println(userById);
        } catch (Exception e) {
        e.printStackTrace();
        } finally {
        sqlSession.close();
        }
    }
    

2. insert

  1. 编写接口

    int insertUser(User user);
    
  2. 编写对应的mapper中的sql语句

    <insert id="insertUser" parameterType="com.th.pojo.User">
         insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
    </insert>
    <!--mybaits中可以自动识别对象中的属性,所以这里直接(#{id},#{name},#{pwd})即可-->
    
  3. 测试/实现

     @Test
     public void testInsertUser() {
         SqlSession sqlSession = MybatisUtils.getSqlSession();
         try {
             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
             int i = mapper.insertUser(new User(4, "王五", "123456"));
             if (i > 0) {
                 System.out.println("insert successful");
             }
             //提交事物,这是必须的,否者无法真正的插入数据。
             sqlSession.commit();
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             sqlSession.close();
         }
     }
    

3. update

  1. sql语句

    <update id="updateUser" parameterType="com.th.pojo.User">
    	update mybatis.user set name =#{name},pwd=#{pwd} where id=#{id};
    </update>
    

4. delete

  1. sql语句

    <delete id="deleteUser" parameterType="int">
       delete from mybatis.user where id=#{id};
    </delete>
    

万能的Map

避免形如在修改某个对象属性时需要传递完整的对象,使用Map则只需要传递有需求的参数即可。以更新数据为例。

  1. 编写接口

    int updateUser2(Map<String,Object> map);
    
  2. 编写对应的mapper中的sql语句

    <update id="updateUser2" parameterType="map">
        <!--参数名于传入map中的key一致即可-->
    	update mybatis.user set name =#{userName} where id=#{userid};
    </update>
    
  3. 测试/实现

    public void testUpdateUser2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            Map<String, Object> map = new HashMap<>();
            map.put("userid", 4);
            map.put("userName", "燕g");
            
            int i = mapper.updateUser2(map);
            if (i>0){
                System.out.println("Update Successful");
            }
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    
  4. 总结

    • Map传递参数,直接在sql中取出key即可。
    • 对象传递参数,直接在sql中取出对象属性即可。
    • 只有一个基本参数情况下,可以直接在sql中取到。
    • 多个参数用Map或者注解!

4、Mybatis配置解析

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

注意:在配置时必须按照一下顺序(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)

1. 属性(properties)

  1. 作用:实现引用配置文件

  2. 使用步骤

    1. 编写一个配置文件

      driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
      
    2. 在核心配置文件中进行引入

      <properties resource="db.properties">
          <property name="uname" value="root"/>
          <property name="uword" value="mysqlpw"/>
      </properties>
      
    3. 核心配置文件中配置environments时直接使用

      <environments default="development">
      	<environment id="development">
              <transactionManager type="JDBC"/>
              <dataSource type="POOLED">
                  <!--之所以使用${driver}是因为在<properties>标签内引入了值来替换-->
                  <property name="driver" value="${driver}"/>
                  <property name="url" value="${url}"/>
                  <property name="username" value="${uname}"/>
                  <property name="password" value="${uword}"/>
              </dataSource>
          </environment>
      </environments>
      
      3. 可以在引入后添加property标签对外部配置文件进行补充,如果有两个相同属性,优先使用外部配置文件!
      

2. 设置(settings)

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名A_COLUMN 映射到经典 Java 属性名aColumn。 true | false False
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

日志工厂

<settings>
    <!--标准日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

标准日志工厂实现不需要额外导包,注意name和value的值必须严格要求按照标准书写,前后不允许出现空格。

LOG4J

是什么?

  • Log4是 Apache的一个开源项目,通过使用Log4,我们可以控制日志信息输送的目的地是控制台、文件、GU组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

如何用?

  1. 导入依赖

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. 创建log4j.properties配置文件(网上有详细配置)

    log4j.rootLogger=DEBUG,console,file
    #控制台输出的相关设置
    #输出出现乱码时在配置文件配置编码格式后还应该设置idea的编码格式。
    log4j.appender.stdout.Encoding=UTF-8
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    # 设置控制台输出的格式为utf-8.
    log4j.appender.console.Encoding=UTF-8
    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
    # 设置文件输出的格式为utf-8.
    log4j.appender.file.Encoding=UTF-8
    log4j.appender.file.File=./log/log4j.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.sql.PreparedStatement=DEBUG
    
  3. 项目中配置LOG4J日志工厂实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  4. 简单使用

    在相应的控制台和文件中将有日志输出。

  5. 其他使用

    1. 在要使用LOG4J的类中创建Logger对象(import org.apache.log4j.Logger;)

      //UserMapperTest为需要使用LOG4J的类
      static Logger logger = Logger.getLogger(UserMapperTest.class);
      
    2. 使用日志级别

      logger.info("进入了testGetUserById");
      logger.debug("进入了testGetUserById");
      logger.error("进入了testGetUserById");
      

3. 类型别名(typeAliases)

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

  1. 自定义别名 (适合Java Bean少的情况下)

    <typeAliases>
        <!--<typeAlias type="com.th.pojo.User" alias="user"/>-->
    </typeAliases>
    
  2. 包扫描别名

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

    每一个在包 com.th.pojo 中的 Java Bean,在没有注解的情况下,会使用Bean 的首字母小写非限定类名来作为它的别名。 比如 com.th.pojo.User 的别名为user;若有注解,则别名为其注解值。

    @Alias("user")
    public class User {
        ...
    }
    

4. 环境配置(environments)

​ 要求:配置多套运行环境

​ MyBatis可以适配多种环境,但是每一个SqlSessionFactory实例只能选择一种环境。

​ MyBatis默认的事物管理器是JDBC,有连接池POOLED

5. 映射器(mappers)

​ 作用:注册绑定Mapper文件

  1. 使用相对于类路径的资源引用

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

    推荐使用,不容易出错。

  2. 使用映射器接口实现类的完全限定类名

    <mappers>
      <mapper class="com.th.dao.UserMapper"/>
    </mappers>
    

    注意点:接口和它的 Mapper配置文件必须同名且在同一个包下!

  3. 内的映射器接口实现全部注册为映射器

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

    注意点:接口和它的 Mapper配置文件必须同名且在同一个包下!

  4. 使用完全限定资源定位符(URL)

    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
    

    不使用。

5、resultMap

问题:数据库中的字段名与实体类中属性名不一致。导致这个字段查出数据为null。这是因为mybatis类型处理器无法找到这个属性。

作用:解决属性名与字段名不一致的问题。将数据库中字段yi

解决:

1. 使用mysql语法as取别名
2. 使用resultMap

使用resultMap

  1. Mapper.xml中配置结果集映射,其中id为这个结果集映射的名字,type为返回值的类型。

    <resultMap id="UserMap" type="user">
        <!--column就是对应数据库中的字段,property就是对应实体类的属性-->
        <result column="pwd" property="password"/>
    </resultMap>
    
  2. 使用结果集映射

    <!-resultMap中的值为以上结果集映射中定义的id值-->
    <select id="getUserById" parameterType="int" resultMap="UserMap">
        select * from mybatis.user where id=#{id}
    </select>
    

6、分页

分页是为了减少数据的处理量

SELECT * from table limit startIndex, pagesize;
SELECT * from table limit 3; #[O, n]

使用Mybatis实现分页,核心SQL

  1. 编写接口

    //使用万能的Map获取起始下标以及页面大小
    List<User> getUserByLimit(Map<String,Integer> map);
    
  2. Mapper.xml

    <!--分页实现查询用户列表-->
    <select id="getUserByLimit" parameterType="map" resultMap="UserMap">
        select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>
    
  3. 测试

    public void testGetUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
            HashMap<String, Integer> map = new HashMap<>();
            map.put("startIndex", 1);
            map.put("pageSize", 2);
    
            List<User> userList = mapper.getUserByLimit(map);
            for (User user : userList) {
                System.out.println(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

其他分页实现:RowBounds、分页插件(PageHelper)。

7、使用注解开发

  1. 注解的使用直接在接口中实现

    @Select("select * from user")
    List<User> getUserList();
    
  2. 在核心配置文件中绑定接口

    <mappers>
        <mapper class="com.th.dao.UserMapper"/>
    </mappers>
    
  3. 测试使用

本质:底层还是使用反射

CRUD

可以在工具类创建的时候设置自动提交事物:

public static SqlSession getSqlSession(){
    //true表示自动提交事物
    return sqlSessionFactory.openSession(true);
}
  1. 编写接口
@Select("select * from user")
List<User> getUserList();

/**当有多个参数时,所有的参数前必须加上@Param("id") 注解*/
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int ids);


@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})")
int addUser(User user);

@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);

@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int uid);
  1. 测试类
public void testT() {
    //第一步:获得SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //List<User> userList = userMapper.getUserList();
        //for (User user : userList) {
        //    System.out.println(user);
        //}
        
        //User user = userMapper.getUserById(1);
        //System.out.println(user);
            
        //UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //userMapper.addUser(new User(5, "sfgu", "abyh"));
        
        //userMapper.updateUser(new User(5, "nihao", "nihao"));
        
        //userMapper.deleteUser(5);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //关闭SqlSession
        sqlSession.close();
    }
}

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议加上。
  • 在SQL语句中的引用就是@Param("id")中设定的属性名。

关于#{}和${}的区别

  • {}是安全的,可以防止SQL注入

  • 建议使用#{}

8、复杂查询

解决形如一个老师对应多个学生,多个学生对应一个老师的情况;

处理实体类

//学生实体类
@Data
public class Student {
    private int id;
    private String name;
    //老师对象
    private Teacher teacher;
}

//老师实体类
@Data
public class Teacher {
    private int id;
    private String name;
}

1. 多对一

子查询

<!--思路:
    1. 查询出所有的学生
    2. 根据查询出的学生的tid,寻求对应的老师  子查询-->
<select id="getStudentList" resultMap="StudentTeacher">
    select * from student
</select>

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性,我们需要单独处理   对象:association    集合:collection-->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{id}
</select>

javaType:Student实体类中teacher属性是Teacher类型。

select:子查询语句的id。

联表查询----按照结果查询

<!--==============方式二:按照结果进行嵌套==============-->
<select id="getStudentList2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname,t.id tid
    from student s,teacher t
    where s.tid=t.id;
</select>

<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
        <result property="id" column="tid" />
    </association>
</resultMap>

查询结果:

Student(id=1, name=小明, teacher=Teacher(id=1, name=秦老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=秦老师))
Student(id=3, name=小张, teacher=Teacher(id=1, name=秦老师))
Student(id=4, name=小李, teacher=Teacher(id=2, name=黄老师))
Student(id=5, name=小王, teacher=Teacher(id=1, name=秦老师))

2. 一对多

获取指定老师的信息以及所有学生信息。

处理实体类

//学生实体类
@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}

//老师实体类
@Data
public class Teacher {
    private int id;
    private String name;
    //老师对应的学生集合
    private List<Student> students;
}

联表查询

<!--=============== 方式一 ===============-->
<select id="getTeacher" resultMap="TeacherStudent">
    select s.id sid,s.name sname,t.name tname,t.id tid
    from student s, teacher t
    where s.tid=t.id and t.id=#{tid}
</select>

<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--复杂的属性我们需要单独处理  老师对应的学生是一个集合,所以这用collection
        javaType:指定属性的类型
        ofType:  集合中的泛型信息
    -->
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

子查询

<!--===============方式二===============-->
<select id="getTeacher2" resultMap="TeacherStudent2">
    select * from mybatis.teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
    select * from mybatis.student where tid=#{tid}
</select>

建议使用联表查询。

9、动态SQL

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

搭建环境

CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

实体类

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    /**属性名和数据库字段名不一致,可以在配置文件中设置mapUnderscoreToCamelCase值
    <setting name="mapUnderscoreToCamelCase" value="true"/>  */
    private Date createTime;
    private int views;
}

使用UUID类创建ID

public class IDutils {
    public static String getId(){
        return UUID.randomUUID().toString().replace("-", "");
    }

    @Test
    public void test(){
        System.out.println(IDutils.getId());
    }
}

1. if

Mapper.xml

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

if标签内test表示条件。

但是这里还是会出现一个问题就是多余了 where 1=1,这是不正规,但是如果没有这个条件在拼接SQL时出现ANDOR,导致语法错误。

改造,使用where标签。

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

测试

public void testqueryBlogIf(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            HashMap map = new HashMap();
//            map.put("titleaa", "Java");
            map.put("authoraa", "狂神说");
            List<Blog> blogs = mapper.queryBlogIf(map);
            for (Blog blog : blogs) {
                System.out.println(blog);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }

2. choose (when, otherwise)

  • choose :MyBatis 提供了的choose 元素像 Java 中的 switch 语句。
  • when:当以第一满足条件时直接拼接后续即便满足也不会再拼接。
  • otherwise:当when条件都满足时拼接。

Mapper.xml

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="titleaa!=null">
                and title=#{titleaa}
            </when>
            <when test="authoraa!=null">
                and author=#{authoraa}
            </when>
            <otherwise>
                and views=#{viewaa}
            </otherwise>
        </choose>
    </where>
</select>

测试

public void testqueryBlogChoose(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            HashMap map = new HashMap();
//            map.put("titleaa", "Java");
//            map.put("authoraa", "狂神说");
            map.put("viewaa", 9999);
            List<Blog> blogs = mapper.queryBlogChoose(map);
            for (Blog blog : blogs) {
                System.out.println(blog);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }

3. trim (where, set)

Mapper.xml

<update id="updateBlog" 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>

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

可以自定义 trim 元素来定制where元素的功能。

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

测试

public void testUpdateBlog(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    try {
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("title", "JavaWeb");
        map.put("author", "狂神说Java");
        map.put("id", "89fde7b2e83e4ae3b2505b3fec27967f");
        mapper.updateBlog(map);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
    }
}

4. foreach

对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

​ Mapper.xml

<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>

测试

public void testQueryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    try {
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap hashMap = new HashMap();
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(3);
        list.add(4);
        hashMap.put("ids", list);
        List<Blog> blogs = mapper.queryBlogForeach(hashMap);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
    }
    sqlSession.close();
}

拼接的SQL

==> Preparing: select * from mybatis.blog WHERE ( id=? or id=? or id=? )
==> Parameters: 1(Integer), 3(Integer), 4(Integer)

5、SQL片段

将某些公共SQL提取出来,方便复用。

  1. 使用sql标签抽取出公共部分。

    <sql id="queryBlogIf-sql">
        <if test="titleaa!=null">
            title=#{titleaa}
        </if>
        <if test="authoraa!=null">
            and author=#{authoraa}
        </if>
    </sql>
    
  2. 在需要使用的时候使用include标签引用即可。

    <select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <include refid="queryBlogIf-sql"></include>
        </where>
    </select>
    
  3. 注意

    1. 最好基于单表定义SQL片段,多张表时不适用。
    2. 不要存在where标签。

10、缓存

1. 一级缓存

一级缓存也叫本地缓存: SqlSession

  • 与数据库同一次会话期间查询的数据会放在本地缓存中。
  • 以后如果需要获取相同数据,直接从缓存中取,不在查询数据库。
  • 一级缓存默认开启,也无法关闭。

测试

  1. 开启日志

  2. 测试在一个SqlSession中查询两次相同记录

    public void testQueryUserById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            try {
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                User user = mapper.queryUserById(2);
                System.out.println(user);
                System.out.println("==========一级缓存在sqlSession中有效,默认开启============");
                User user2 = mapper.queryUserById(2);
                System.out.println(user2);
                System.out.println("===============user == user2=======");
                System.out.println(user == user2);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                sqlSession.close();
            }
        }
    
  3. 查看日志输出

  1. 缓存失效
    1. 查询不同记录
    2. 增删改改变原来的记录,导致刷新缓存
    3. 查询不同的Mapper.xml
    4. 手动清理缓存 sqlSession.clearCache();

2. 二级缓存

二级缓存也叫全局缓存,解决一级缓存作用域低的问题

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存。
  • 工作机制:
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。
    • 如果会话关闭,这个对话的一级缓存就消失了,但是一级缓存中的可以保存到二级缓存。
    • 新的会话查询信息,就可以从二级缓存中获取内容。
    • 不同的mapper查询出的数据会放在自己对应的缓存(map)中。

测试

  1. 显示开启全局缓存

    <settings>
        <!--显示的开启全局缓存,既二级缓存,虽然这个默认是开启的-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 在当前Mapper中开启二级缓存<cache/>

    <cache/>
    

    当然也可以自定义参数

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    

查询顺序: 先查看二级缓存中是否存在===》一级缓存中是否存在 ===》 数据库

3. 自定义缓存——Ehcache

Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存。

  1. 导入依赖

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
  2. 在当前Mapper.xml文件中使用自定义缓存

    <cache type="org.mybatis.caches.ehcache.EhcacheCache" />
    
  3. 添加配置文件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"
             updateCheck="false">
    
        <diskStore path="./tmpdir/Tmp_EhCache"/>
    
        <defaultCache
                eternal="false"
                maxElementsInMemory="10000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="259200"
                memoryStoreEvictionPolicy="LRU"/>
    
        <cache
                name="cloud_user"
                eternal="false"
                maxElementsInMemory="5000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="1800"
                memoryStoreEvictionPolicy="LRU"/>
    </ehcache>
    
posted @ 2022-03-28 17:15  三淳  阅读(25)  评论(0编辑  收藏  举报