1.Mybatis简介

  • 说明:笔记是B站狂神说相关视频的学习笔记
  • 持久化:就是将程序的数据在持久状态和瞬时状态转化的过程
  • 持久层:完成持久化工作的代码块,统称为Dao层
  • MyBatis:简化数据库连接和操作数据库的操作的半持久框架,是目前的主流

2.第一个Mybatis项目

2.1.配置数据库

= 创建数据库,表,表数据

CREATE TABLE `user` (
        `id` int(20) NOT NULL AUTO_INCREMENT,#id不为0,自增
        `name` varchar(30) DEFAULT NULL,#name默认null
        `pwd` varchar(30) DEFAULT NULL,#pwd默认null
        PRIMARY KEY (`id`)#主键索引=id
)ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES(1,'狂神','123456');
INSERT INTO `user` VALUES(2,'张三','123456');
INSERT INTO `user` VALUES(3,'李四','123456');

2.2.搭建环境

  • 父工程导包

<dependency>
    <!--mysql驱动-->
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>
<dependency>
    <!--mybatis官网-->
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <!-- junit测试-->
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  • 子工程加载资源过滤器

防止后缀为properties和xml配置文件无法加载

<!--资源过滤器,防止导入资源失败问题,最好在父子pom.xml里都加入一下代码-->
<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>      
  • 配置resources /mybais-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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="admin"/>
            </dataSource>
        </environment>
    </environments>
    <!--mapper注入mybatis-->
    <mappers>
        <mapper resource="com/ssl/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 编写工厂工具类

    • spring整合mybatis后,这个操作再mybats-config.xml中配置
public class MyBatisUtil {
    /**
     * 提升sqlSessionFactory作用域,便于全局使用
     */
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            /*
            使用Mybatis第一步,获取sqlSessionFactory对象
             */
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sqlSessionFactory对象获取SQLSession实例
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.3.Dao层

  • Pojo:User
  • Dao:接口;实现类变成了XXXMapper.xml文件
public interface UserDao {
    List<User> getUserList();
}
<?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 namespace="com.ssl.dao.UserDao">
    <select id="getUserList" resultType="com.ssl.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2.4 测试

  • 资源过滤异常,见上面的子工程配置资源过滤器

  • mapper注册失败异常:在mybatis-config.xml配置

<mappers>
    <mapper resource="com/ssl/dao/UserMapper.xml"/>
</mappers>
public class UserMapperTest {
    @Test
    public void getUserList() {
        //1 获取是sqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //2 获取的是接口的.class,因为多态
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //3 建议:最后关闭sqlSession
        sqlSession.close();
    }
}

3. CRUD

3.1 namespace=接口全类名相同

<mapper namespace="com.ssl.dao.UserMapper">
    <select id="getUserList" resultType="com.ssl.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

3.2 select

  • id:接口中的方法名
  • parameterType:返回结果
  • resultType:方法参数

3.3 insert/update/delete

  • 增删改需要添加事务,返回值只有int,不用添加resultType
<mapper namespace="com.ssl.dao.UserMapper">
    <!--增删改需要提交事务,没有指定的返回值,都是int,所以不用添加resultType-->
    <!--添加一个用户,对象中的属性可以直接取出来-->
    <insert id="addUser" parameterType="com.ssl.pojo.User">
        INSERT INTO mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd});
    </insert>
    <update id="updateUser" parameterType="com.ssl.pojo.User">
        update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id};
    </update>
    <delete id="deleteUserById" parameterType="int">
        delete from mybatis.user where id=#{id}
    </delete>
</mapper>

3.4 常见错误

  • 增删改sql语句写错
# 增加需要values 
insert into mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd});
# 修改需要set
update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id};
# 删除需要from
delete from mybatis.user where id=#{id} 
  • 出现bug,是从后往前看查看原因

3.5 万能map

  • 如果数据库字段太多,添加修改需要的bean太多,使用map来封装参数,
  • 好处一:避免多余代码
  • 好处二:跳过特定的Bean属性,可以随意命名key,保证value是字段的属性就行

3.6 模糊查询

<!--模糊查询1 不推荐-->
<select id="getLikeUser1" parameterType="string" resultType="com.ssl.pojo.User">
    select * from mybatis.user where name like #{value};
</select>
<!--模糊查询2 建议写死-->
<select id="getLikeUser2" parameterType="string" resultType="com.ssl.pojo.User">
    select * from mybatis.user where name like "%"#{value}"%";
</select>

4 配置解析

4.1 mybatis_config.xml

  • 在resource中创建mybatis_config.xml

4.2 mybatis中的配置属性

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

4.3 enviroments(环境)

  1. 事务管理器:transcationManager,默认“JDBC”
  2. 连接数据库:默认pooled
<environments default="development">
        <environment id="development">
            <!--事务管理器:默认JDBC-->
            <transactionManager type="JDBC"/>
            <!--连接数据源:默认POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
</environments>

4.4 properties(属性)

  • db.properies
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8
username=root
password=123456

4.5 settings(设置)

settings配置 解释说明 默认状态
cacheEnabled(重要) 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 默认开启
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 默认关闭
mapUnderscoreToCamelCase(重要) 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 默认关闭
logImpl(最重要) 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J、LOG4J、STDOUT_LOGGING等,默认关闭

4.6 typeAliases(类型别名)

  • 作用:mapper.xml配置resultType时,简化书写

图片说明

<!--给各种类取别名,简化使用配置-->
<typeAliases>
    <!--方式一:指定类
        <typeAlias alias="User" type="com.ssl.pojo.User"/>
        -->
    <!--方式二;指定包,包中的小写作为别名
                    也可以更改小写名,在类上使用@value(“别名”)
        -->
    <package name="com.ssl.pojo"/>
</typeAliases>

4.7 plugins(插件)

后期需要加深学习的两大插件,使Mybatis配置更加简单

  • Mybatis-Plus
  • MyBatis Generator Core

4.8 mappers(映射器)

  1. 在mybatis-config.xml中配置mapper映射器
<!-- 使用resource相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  1. class和package绑定:接口和mapper配置文件必须同名,是否必须在同一个包下有待学习?
<!-- 使用class映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 使用package+name将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.9 MyBatisUtil(参数作用域)

名称 解释说明
SqlSessionFactoryBuilder 一旦创建就不再需要它了,作用域是局部变量=静态代码块先加载
SqlSessionFactory 运行期间一直存在,作用域是应用作用域,使用单例模式或者静态单例模式。
SqlSession 连接到数据库的请求,线程不安全,用完后马上关闭;作用域是方法或者请求中,用完就关闭,关闭操作十分重要
public class MyBatisUtil {
    //0 提升第三步中sqlSessionFactory作用域
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //1 获取mybatis配置文件
            String resource = "mybatis-config.xml";
            //2 获取配置文件的输入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //3 使用SqlSessionFactoryBuilder().build()创建sqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //4 通过getSqlSession获取session
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);
    }
}

5 属性名和字段名不一致

5.1 更改User中的pwd->password

  • 模拟实现这个过程,数据库中是pwd,更改Pojo中的User属性为pwd,导致不一致
//查询结果:不一致的字段查询结果为null
User{id=1, name=&#39;狂神&#39;, password=&#39;null&#39;}

5.2 解决办法

  • 更改pojo成员属性名 = 数据库字段名:使用起来太low,不推荐
  • 使用结果集映射=resultMap:哪个字段不一致,就使不一致的成员属性property映射到数据库的column

图片说明

<?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接口=原来的dao实现类-->
<mapper namespace="com.ssl.dao.UserMapper">
    <!--更改结果集映射,解决属性名和数据库列名不一致-->
    <resultMap id="UserMap" type="com.ssl.pojo.User">
        <result column="pwd" property="password"/>
    </resultMap>
    <!--通过id查询一个用户-->
    <select id="getUserById" parameterType="int" resultMap="UserMap">
            select * from mybatis.user where id = #{id};
    </select>
</mapper>

6 开启日志

6.1 日志工厂

  • 在setting中配置:name = logImpl 大小写和空格不能错

  • LOG4J:必须掌握,步骤:setting配置,导包,配置pro,测试时加载

    • setting配置,+导依赖包

       <settings>
             <setting name="logImpl" value="log4j"/>
       </settings>
       <!--log4j日志-->
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>
    • log4j.properties:需要时去网上找一份

      # log4j日志系统:通用配置
      # Define the root logger with appender file
      # log=D:\logs
      log4j.rootLogger = DEBUG, FILE, console
      # 输出到当前目录文件下的log包中
      log4j.appender.FILE=org.apache.log4j.FileAppender
      log4j.appender.FILE.File=./logs/log4j.log
      # Set the immediate flush to true (default)
      log4j.appender.FILE.ImmediateFlush=true
      # Set the threshold to debug mode
      log4j.appender.FILE.Threshold=debug
      # Set the threshold to debug mode
      # 设置日志信息追加
      log4j.appender.FILE.Append=true
      # Set the maximum file size before rollover
      # 30MB
      log4j.appender.FILE.MaxFileSize=5KB
      # Set the backup index
      log4j.appender.FILE.MaxBackupIndex=2
      # Define the layout for file appender
      log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
      log4j.appender.FILE.layout.conversionPattern=%m%n
      # 将日志输出到控制台
      log4j.appender.console=org.apache.log4j.ConsoleAppender
      log4j.appender.console.layout=org.apache.log4j.PatternLayout
      log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]-[%t]-[%F:%L]-[%p]-[%c]-%m%n
      #log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd}]-[%t]-[%x]-[%-5p]-[%-10c:%m%n]
      log4j.appender.console.encoding=UTF-8
      
    • test中:static Logger logger = Logger.getLogger(UserMapperTest.class);

      public class UserMapperTest {
          //使用log4j
          static Logger logger = Logger.getLogger(UserMapperTest.class);
      
          @Test
          public void getUserById() {
              //1 获取是sqlSession对象
              SqlSession sqlSession = MyBatisUtil.getSqlSession();
              //2 获取方式一:getMapper
              UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
              User user = userMapper.getUserById(1);
              System.out.println(user);
              //3 建议:最后关闭sqlSession
              sqlSession.close();
          }
      }
  • STDOUT_LOGGING :掌握,不用导包,mybatis默认配置了,缺点就是只在控制台显示

 <settings>
       <!-- <setting name="logImpl" value="log4j"/>-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
 </settings>
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • SLF4J
  • NO_LOGGING

7 分页

7.1 limit

  • 第一个参数:startIndex=开始分页的下标 = (第几页 - 1 )* pageSize
  • 第二个参数:pageSize=分页中每页的大小
  • 只有一个参数:默认是从第一个元数到该index下标实现分页
select * from user limit startIndex,pageSize;# startIndex=(第几页-1)*pageSize
select * from user limit index;# 默认从第一个元素到第index个用户

7.2 实现分页

  • map和RowbBounds两者都可以实现分页:推荐使用map分页,因为默认key=#{key}
public interface UserMapper {
    /**
     * 根据id获取一个用户
     * @param id 指定
     * @return User
     */
    User getUserById(int id);

    /**
     * 通过map分页数据,推荐使用
     * @param map 常用
     * @return ListUser
     */
    List<User> getLimitUser(Map<String,Object> map);

    /**
     * 了解,不推荐使用,通过RowBounds分页数据
     * @return ListUser
     */
    List<User> getLimitUserByRowBounds();
}
<?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.ssl.dao.UserMapper">
    <!--更改结果集映射,解决属性名和数据库列名不一致-->
    <resultMap id="UserMap" type="com.ssl.pojo.User">
    <result column="pwd" property="password"/>
    </resultMap>
    <!--map分页-->
    <select id="getLimitUser" parameterType="map" resultMap="UserMap">
         select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>
    <!--RowBounds分页-->
    <select id="getLimitUserByRowBounds" resultMap="UserMap">
         select * from mybatis.user ;
    </select>
</mapper>
public class UserMapperTest {    
    @Test
    public void getLimitUser() {
        UserMapper userMapper = MyBatisUtil.getSqlSession().getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        //mapper.xml会自动寻找map中的key=#{key}
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> limitUser = userMapper.getLimitUser(map);
        for (User user : limitUser) {
            System.out.println(user);
        }
    }
     @Test
    public void getLimitUserBrRowBounds() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //老式的查询分页:使用RowBounds
        RowBounds rowBounds = new RowBounds(0, 2);
        //语法麻烦
        List<User> usersList = sqlSession.selectList("com.ssl.dao.UserMapper.getLimitUserByRowBounds",0,rowBounds);
        for (User user : usersList) {
            System.out.println(user);
        }
    }
}

7.3 分页插件

  • 自学Mybatis PageHelper插件,公司需要就去网站自学

8 注解配置sql语句

8.1 mybaits-config.xml中配置映射器

<!--绑定注解开发的接口 class-->
<mappers>
    <mapper class="com.ssl.dao.UserMapper"/>
</mappers>

8.2 缺点

  • 如果表中列名和成员属性名不一致,查出来就是null

8.3 注解的CRUD

  • 学习@param(非常重要)
    • 基本类型、String建议都加上,引用类型不用加
    • (uid)中的就是sql中的#{uid}
public interface UserMapper {
    /**
     * 使用注解开发,有局限性就是column 必须与 dao接口成员属性名一致,否知输出就是null,查不出来
     * 所以注解语句开发,便于使用简单场景
     */
    @Select("select * from user")
    List<User> getUsers();

    /**
     * @param id=uid
     * @return User
     */
    @Select("select * from user where id = #{uid}")
    User getUserById(@Param("uid") int id);
}
  • 增删改自动提交事务
 public static SqlSession getSqlSession() {
        //不推荐使用,建议手动提交commit
        return sqlSessionFactory.openSession(true);
    }

9 Lombok

9.1 IDEA中安装Lombok插件

9.2 maven安装依赖

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

9.3 @Data等注解

  • @Data:最常用,自动加上Setter Getter equals tostring,
  • @AllArgsConstructor:有参构造
  • @NoArgsConstructor:无参构造

9.4 缺点

  • 虽然可以混合使用,但多重的构造器的构造器不能重载
  • 公司用就用,不用就少用,因为改变了java源码的书写习惯,不利于推广

10 多对一

  • 导入Lombok插件和依赖,减少pojo代码
  • 新建实体类Student、Teacher和数据库表
    • Student中有一个字段tid使用外键约束,关联Teacher
CREATE TABLE `student` (
  `id` int(10) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `tid` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `teacher` (
  `id` int(10) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • 配置环境:pojo和mapper接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    //需要关联一个老师类
    private Teacher teacher;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}
public interface StudentMapper {
    //子查询
    List<Student> getStudents();
    //联表查询
    List<Student> getStudents1();
}

10.1:子查询=查询嵌套

  • mapper.xml
    • 以下对于理解非常重要
    • javaType=“teacher”配置了别名,大小写都可以,去pojo路径中中找Teacher,然后使用其成员属性tid

图片说明

<!--方式一:按照查询嵌套处理 = 子查询-->
<select id="getStudents" resultMap="resultStudents">
        select * from mybatis.student;
</select>
<resultMap id="resultStudents" type="student">
        <!--association:属性是对象时使用-->
        <!--collection:属性是集合时候用-->
     <association property="teacher" column="tid" javaType="teacher" select="getTeachers"/>
</resultMap>
<select id="getTeachers" resultType="teacher">
    select * from mybatis.teacher where id = #{tid}
</select>
  • 测试
     @Test
    public void getStudents() {
        StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class);
        List<Student> students = studentMapper.getStudents();
        for (Student student : students) {
            System.out.println(student);
        }
        /*
            Student(id=1, name=小红, teacher=Teacher(id=1, name=秦老师))
         */
}

10.2:联表查询=结果嵌套

  • 使用多表查询,避免写多个sql

  • mapper.xml

图片说明

<!--方式二:按照结果嵌套处理 = 联表查询-->
<select id="getStudents1" resultMap="resultStudents1">
    select  s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id;
</select>
<resultMap id="resultStudents1" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>
  • 测试:与嵌套查询结果没变
 @Test
    public void getStudents1() {
        StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class);
        List<Student> students = studentMapper.getStudents1();
        for (Student student : students) {
            System.out.println(student);
        }
        /*
            Student(id=1, name=小红, teacher=Teacher(id=0, name=秦老师))
         */
    }

11 一对多

  • 配置环境:pojo和dao下的mapper接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private int tid;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    //一对多的集合
    private List<Student> students;
}
public interface StudentMapper {

}

public interface TeacherMapper {
    /**
     * 子查询:按查询嵌套查询
     * @param tid
     * @return
     */
    Teacher getTeacher(@Param("id") int tid);
    /**
     * 联表查询:按结果嵌套查询
     * @param tid
     * @return
     */
    Teacher getTeacher1(@Param("id") int tid);
}

11.1: 子查询

图片说明

<!--子查询:按查询嵌套查询-->
<select id="getTeacher" resultMap="teacherToStudent">
    select id, name from mybatis.teacher where id = #{id}
</select>
<resultMap id="teacherToStudent" type="teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--  column="id"是teacher表中的id-->
    <collection property="students" javaType="List" ofType="student"
                column="id" select="getStudentsByTeacherId" />
</resultMap>
<select id="getStudentsByTeacherId" resultType="student">
    select * from mybatis.student where tid = #{id}
</select>

测试:

@Test
public void getTeacher() {
    TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class);
    Teacher teacher = teacherMapper.getTeacher(1);
    System.out.println(teacher);
    /*
        如果id=0怎么解决?就是collection中配置result property="id" column="id"
        Teacher(id=1, name=秦老师, students=[Student(id=1, name=小红, tid=1),Student(id=2, name=小明, tid=1)...
         */
}

11.2: 联表查询

<!--联表查询:按结果嵌套查询-->
<select id="getTeacher1" resultMap="teacherAndStudent">
    select  s.id sid,s.name sname,t.name tname,t.id tid
    from mybatis.teacher t,mybatis.student s
    where s.tid=t.id and t.id =#{id}
</select>
<resultMap id="teacherAndStudent" 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>

测试:

@Test
public void getTeacher1() {
    TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class);
    Teacher teacher = teacherMapper.getTeacher1(1);
    System.out.println(teacher);
    /*
        Teacher(id=1, name=秦老师, students=[Student(id=1, name=小红, tid=1), Student(id=2, name=小明, tid=1)...
         */
}

11.3: 多表查询小结

  • 多对一中的一:association
  • 一对多中的多:collection
  • javaType & ofType
    • javaType:指定实体类中的属性的java返回值类型
    • ofType:映射List或某些指定的pojo泛型的类型,联想List中的泛型类型Student用ofType绑定
  • 注意点:
    • 保证SQL的可读性,建议使用联表查询

11.4: 面试题自学补充

  • MySQL引擎
  • InnoDB底层原理
  • 索引和索引优化

12 动态SQL

  • 概念:动态 SQL 是 MyBatis 的强大特性之一,简化了原生复杂SQL书写
  • 四个判断条件:
    • if
    • choose (when, otherwise)
    • trim (where, set)
    • foreach
  • 搭建数据库
CREATE TABLE blog(
    id VARCHAR(50) NOT NULL COMMENT &#39;博客id&#39;,
    title VARCHAR(100) NOT NULL COMMENT &#39;博客标题&#39;,
    author VARBINARY(30) NOT NULL COMMENT&#39;博客作者&#39;,
    # 数据库时间DateTime类型=pojo中的Date类型
    # 下划线命名调到pojo中的驼峰式命令,需要mybatis开启驼峰式命令
    create_time DATETIME NOT NULL COMMENT&#39;创建时间&#39;,
    views INT(30) NOT NULL COMMENT&#39;浏览量&#39;
)ENGINE=INNODB DEFAULT CHARSET = utf8;
  • pojo和驼峰式命名
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    /**
     * 下划线命名调到pojo中的驼峰式命令,需要mybatis开启驼峰式命令
     */
    private Date createTime;
    private int views;
}
 <!--解决驼峰命令,使用setting配置,只能用户数据库中的xx_xx编程bean中的驼峰式-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • Utils
public class MyBatisUtil {
    /**
     * 提升sqlSessionFactory作用域,便于全局使用
     */
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            /*
            使用Mybatis第一步,获取sqlSessionFactory对象
             */
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sqlSessionFactory对象获取SQLSession实例
     */
    public static SqlSession getSqlSession() {

        return sqlSessionFactory.openSession(true);
    }
}
//随机产生数据库表中的views
public class UUIDUtils {
    public static String getId() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    @Test
    public void getUUId() {
        System.out.println(UUIDUtils.getId());
    }
}
  • mapper接口
public interface BlogMapper {
    int addBlog(Blog blog);
    List<Blog> queryBlogByIf(Map map);
    List<Blog> queryBlogByWhere(Map map);
    List<Blog> queryBlogByForeach(Map map);
}

if

  • 概念:sql常见的场景就是判断使用

  • 方式一:通过< if >直接使用,或者< include>跳转sql使用时候,需要保证where成立,所以需要在sql语句中加上类似where 1= 1 或者 where state = &#39;active&#39;等语句

    • 这里使用了SQL片段复用,见后面讲解

图片说明

<!--if:通过include跳转到使用,缺点是必须写上判断条件成立 where 1=1-->
<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog where 1=1
    <include  refid="if_title_author_like" />
</select>
<sql id="if_title_author_like">
    <if test="title !=null">
        and  title like #{title}
    </if>
    <if test="author !=null">
        and author like #{author}
    </if>
</sql>
  • 方式二:通过< where >和< if >混合使用,就不用手动加上where 1= 1
<!--if:通过where直接使用/直接使用if判断,但是不推荐。原理:如果test存在,就自动加上where -->
<select id="queryBlogByWhere" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <if test="id !=null">
            id like #{id}
        </if>
        <if test="views !=null">
            and views like #{views}
        </if>
    </where>
</select>
  • 测试:模糊查询需要封装通配符
@Test
public void queryBlogByIf() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Map<String, String> map = new HashMap<>();
    //模糊查询,使用map的好处
    map.put("title", "%my%");
    List<Blog> blogs = blogMapper.queryBlogByIf(map);
    System.out.println(blogs);
    sqlSession.close();
}

@Test
public void queryBlogByWhere() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Map<String, Object> map = new HashMap<>();
    //map.put("id","%4%");
    map.put("views", "%2%");
    List<Blog> blogs = blogMapper.queryBlogByWhere(map);
    System.out.println(blogs);
    sqlSession.close();
}

choose

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

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

  • 细节:choose只能满足其中一个when\otherwisw;使用WHERE state = ‘ACTIVE’等保证where成立

<!--官网案例-->
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

where

  • 单独使用< if > 的缺点:如果没有查询条件或者第一个条件没有满足,就会出现错误的sql语句:
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
  • 出现错误sql:
# 没有条件成立
SELECT * FROM BLOG
WHERE
# 第一个条件没有成立
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
  • 使用< where >
    • 优势:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
  • 使用自定义 trim 元素来定制 where 元素的功能
    • 与< where>等价的< trim>
      • prefix="WHERE"满足条件,自动添加的字段:prefixOverrides自动忽略的字段,细节:AND |OR 两者后面都包含了一个空格,这是正确sql的书写要点
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set

  • 概念:用于动态update语句的叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
    • 等价的trim语句,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)= 也就是说其实mybatis自动在update中set就给你加上了逗号,但是你自己手写加上了,< set> 也会给你忽略掉
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

foreach

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

  • 原生:SELECT * FROM blog WHERE 1=1 AND (id=1 OR id=2);

  • 细节:if < where> 多个条件成立时,就会忽略掉原生中and书写

图片说明

<select id="queryBlogByForeach" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
           <foreach collection="ids" item="id" open="(" separator="or" close=")">
            id=#{id}
        </foreach>
    </where>
</select>
<!--官网案例:使用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>

SQL片段

  • 回顾:前面的< where >结合< if>,我们将公共的SQL语句抽取出来,复用.
  • 使用:< sql id= > 标签和< include refid= >引用
  • 细节:
    • 最好基于单表使用sql片段,多表别的表不一定支持
    • 使用sql片段复用,不要使用< where >标签,因为它内置了会忽略掉某些字段
<sql id="if_title_author_like">
    <if test="title !=null">
        and  title like #{title}
    </if>
    <if test="author !=null">
        and author like #{author}
    </if>
</sql>

<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog where 1=1
    <include refid="if_title_author_like" />
</select>

bind(了解)

  • bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="&#39;%&#39; + _parameter.getTitle() + &#39;%&#39;" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

13 缓存

  • 我们再次查询相同数据时候,直接走缓存,就不用再存了,解决速度问题
  • 什么样的数据需要用到缓存?
    • 经常查询并且不经常改变的数据,可以使用缓存

缓存原理图(重要)

二级缓存工作原理:

  • 一次sqlsession是一级缓存,查询操作结束后,是默认保存在一级缓存中的
  • 如果开启二级缓存,必须先关闭一级缓存,这时候的缓存数据会保存到二级缓存中
  • 第二次查询时候,用户操作会嫌去二级缓存中查找

图片说明

一级缓存

  • 默认情况下,只启用了本地的会话(一级)缓存,它仅仅对一个会话中的数据进行缓存。
    • 把一级缓存想象成一个会话中的map,便于理解

图片说明

  • 缓存失效
    • 增删改会把所有的sql缓存失效,下次会重写从数据库中查
    • 查询不同的东西,查询不同的mapper.xml
    • 手动清除缓存:sqlSession.clearCache();

二级缓存

  • mybatis-config,xml开启全局缓存
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启全局缓存 默认是开启的,显示写便于可读-->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • mappper.xml开启二级缓存:

    <!--开启二级缓存-->
    <cache/>
    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
      • 读写缓存需要pojo开启序列化操作
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;

    public static void main(String[] args) {
        new ArrayList<>();
        new HashMap<>();
        new LinkedList<>();
    }
}
  • 开启二级缓存时,可以指定参数

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    • eviction:清楚算法,默认LRU
      • LRU – 最近最少使用:移除最长时间不被使用的对象。
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
      • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
    • flushInterval:刷新缓存间隔,默认无
    • size:最多缓存数量,默认1024
    • readOnly:只读缓存;写操作会不走缓存,直接从数据库查询,默认是读/写缓存
  • 使用二级缓存

    • 测试语句
public class MyTest {
    @Test
    public void queryUserById() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        //关闭上次sqlSession,如果开启二级缓存,就会把这次的一级缓存保存到二级缓存中
        sqlSession.close();
        System.out.println("=================");

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        //如果开启二级缓存,下次相同mapper的查询操作会先重二级缓存中查找
        User user1 = mapper1.queryUserById(1);
        sqlSession1.close();
    }
}

图片说明

图片说明

自定义缓存(了解)

  • 概念:ehcache是一个分布式缓存,主要面向通用缓存
  • 手写或者导入第三方的ehcache缓存依赖
  • 所以可以自定义ehcache.xml配置文件
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.0</version>
</dependency>
  • 在mapper.xml中配置
 <cache type="org.mybatis.caches.encache.EhcacheCache"/>
  • 在resource中创建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">

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

      <!--
         diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
         user.home – 用户主目录
         user.dir  – 用户当前工作目录
         java.io.tmpdir – 默认临时文件路径
       -->
      <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
      <!--
         defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
       -->
      <!--
        name:缓存名称。
        maxElementsInMemory:缓存最大数目
        maxElementsOnDisk:硬盘最大缓存个数。
        eternal:对象是否永久有效,一但设置了,timeout将不起作用。
        overflowToDisk:是否保存到磁盘,当系统当机时
        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
        timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
        clearOnFlush:内存数量最大时是否清除。
        memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
        FIFO,first in first out,这个是大家最熟的,先进先出。
        LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
        LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
     -->
  </ehcache>

Redis

  • 自学,另一个开始