学习Mybatis(二)

[TOC]

分页

为什么要分页

  • 减少数据的处理量

使用Limit分页

select * from user limit startIndex,pageSize;

使用mybatis实现分页,核心SQL

  • 接口

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

  • <!--分页-->
    <select id="getUserByLimit" resultType="User" resultMap="UserMap" parameterType="map">
        select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>
    
  • 测试

@Test
public void getUserByLimit(){
    //获取session对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Integer> map= new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userList= mapper.getUserByLimit(map);
    userList.forEach(System.out::println);
    sqlSession.close();
}

RowBounds分页

不再使用sql分页

  • 接口

  • List<User> getUserByRowBounds();
    
  • Mapper.xml

  • <!--RowBounds分页-->
    <select id="getUserByRowBounds" resultType="User" resultMap="UserMap" parameterType="map">
        select * from mybatis.user ;
    </select>
    
  • 测试

@Test
public void getUserByRowBounds(){
    //获取session对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    RowBounds rowBounds = new RowBounds(1,2);


    //通过java代码层实现分页
    List<User> userList =  sqlSession.selectList("com.company.dao.UserMapper.getUserByRowBounds",null,rowBounds);
    userList.forEach(System.out::println);
    sqlSession.close();
}

Mybatis分页插件,官网介绍很详细

https://pagehelper.github.io/

使用注解开发

面向接口编程

  • 解耦
  • 可扩展,高复用,分层开发

接口的理解

  • 定义(规范,约束)与实现(名实分离的原则)的分离

对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置

注解在接口上实现

@Select("select * from mybatis.user")
List<User> getUsers();

注册,绑定接口

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

测试

@Test
public void getUserList(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //底层主要应用反射
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.getUsers();
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}

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

Mybatis执行流程

  • Reource获取全局配置文件
  • 实例化SqlSessionFactoryBuilder构造器
  • 解析配置文件流 XMLConfigBuilder
  • Configuration所有的配置信息
  • SqlSessionFactory实例化
  • 创建executor执行器
  • 创建SqlSession
  • 实现CRUD
  • 提交事务
  • 关闭

注解CRUD

我们可以在工具类创建的时候实现自动提交事务

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

接口

 @Select("select * from mybatis.user")
    List<User> getUsers();

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


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


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

    @Delete("delete from mybatis.user where id = #{id}")
    int deleteUser(@Param("id") int id);

测试

    @Test
    public void getUserList(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //底层主要应用反射
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        /*List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }*/
        User user = mapper.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    }

    @Test
    public void addUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(11, "e", "44"));
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(11, "778", "4rr"));
        sqlSession.close();
    }

    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(11);
        sqlSession.close();
    }

注意:必须要将接口绑定到核心配置文件

@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上
  • 我们在SQL中引用的就是我们这里的@Param(“uid”)中设定的属性名

Lombok

使用步骤

  • 在IDEA中安装Lombok插件

  • 在项目中导入lombok的jar包

  • 在实体类上加注解

  • <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>
    
    - Features
    @Getter and @Setter
    @FieldNameConstants
    @ToString
    @EqualsAndHashCode
    @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors
    @Wither
    @With
    @SneakyThrows
    @val
    @var
    
@Data:无参构造,get,set,toString,hashCode,equals
@NoArgsConstructor
@AllArgsConstructor

复杂查询环境搭建

多对一

集合

SQL环境


CREATE TABLE `teacher` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	`tid` INT(10) DEFAULT NULL,
	PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8


INSERT INTO teacher(`id`,`name`) VALUES(1,'秦老师');

CREATE TABLE `student` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) 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

INSERT INTO `student` (`id`, `name`, `tid`) VALUES('1', '小明','1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES('2', '小红','1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES('3', '小张','1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES('4', '小李','1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES('5', '小王','1');

测试环境搭建

  • 导入lombok
  • 新建实体类Teacher,Student
  • 建立Mapper接口
  • 建立Mapper.xml文件
  • 在核心配置文件中绑定注册我们的Mapper接口或者文件
  • 测试查询是否能够成功

按照查询嵌套处理

<!--思路
    :查询所有的学生信息
    :根据查询出来的学生的tid,寻找对应的老师
-->
<select id="getStudent" 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>
<!--===========================-->
<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname 
    from student s,teacher t 
    where s.tid=t.tid;       
</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"/>
    </association>
</resultMap>

按照结果嵌套处理

!--===========================-->
<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname
    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"/>
    </association>
</resultMap>

回顾Mysql多对一查询方式

  • 子查询
  • 联表查询

一对多

环境搭建

import lombok.Data;

import java.util.List;

@Data
public class Teacher {
    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}
package com.company.pojo;


import lombok.Data;

@Data
public class Student {
    private int id;
    private String name;
    //学生需要关联一个老师
    private int tid;
}

按结果嵌套处理

<!--按结果嵌套查询-->
<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"/>
    <!--复杂的属性,我们需要单独处理对象:association 集合: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="getStudent" column="id"/>
</resultMap>

<select id="getStudent" resultType="Student">
    select * from mybatis.student where tid = #{id};
</select>

按照结果嵌套处理

按照查询嵌套处理

小结

  • 关联-association 多对一
  • 集合-collection 一对多
  • javaType & ofType
    • javaType 用来指定实体类中属性的类型
    • ofType 用来指定映射到list或则集合中的pojo类型

注意点:

  • 保证sql的可读性,尽量保证通俗易懂
  • 注意一对多,多对一,属性名和字段的问题
  • 如果问题不好排查错误,可以使用日志,建议使用LOG4J

面试高频

  • MySQL引擎
  • Innodb底层原理
  • 索引
  • 索引优化

动态sql

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

动态 SQL 是 MyBatis 的强大特性之一

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

搭建环境


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

创建一个基础工程

  • 导包

  • 编写配置文件

  • <setting name="mapUnderscoreToCamelCase" value="true"/><!--//开启驼峰命名-->
    
  • 编写实体类

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

if语句

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1
    <if test="title!=null">
        and title=#{title}
    </if>
    <if test="author!=null">
        and author=#{author}
    </if>
</select>
@Test
public void queryBlogIF(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,String> map = new HashMap<>();

    map.put("title","Java如此简单");
    List<Blog> blogs = mapper.queryBlogIF(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

常用标签

choose when otherwise

where 元素指挥在至少一个元素的条件下返回sql自居的情况下才去插入where子句,而且若语句的开头为AND或OR,where元素也会将他们去除

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
    <if test="title!=null">
        title=#{title}
    </if>
    <if test="author!=null">
        and author=#{author}
    </if>
    </where>
</select>
@Test
public void queryBlogChoose(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,String> map = new HashMap<>();

    map.put("title","Java如此简单");
    map.put("author","狂神说");
    List<Blog> blogs = mapper.queryBlogChoose(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

choose

<select id="queryBlogChoose" 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>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

动态SQL常用标签

用于动态更新语句的解决方案叫做set,set元素可以用于动态包含需要更新的列,而舍去其他的

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

trim

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

foreach sql片段

sql片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用

  • 使用sql标签抽取公共的部分
  • 在需要使用的地方使用include标签使用即可
<sql id="if-title-author">
    <if test="title!=null">
         title=#{title}
    </if>
    <if test="author!=null">
        and author=#{author}
    </if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog 
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

注意:

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

foreach

动态sql的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建In条件语句的时候

foreach允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量,也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符,这个元素是很智能的,因此它不会偶然的附加多余的分隔符

<!--我们现在传递一个万能的map,这个map中存在一个集合-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>
@Test
public void queryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    ArrayList<Integer> ids = new ArrayList<Integer>();
    map.put("ids",ids);
    List<Blog> blogs = mapper.queryBlogForeach(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();

}

动态sql就是在拼接sql语句,只要保证sql的正确性,按照sql的格式,去排列组合就可以了

缓存

查询:连接数据库,耗资源

一次查询的结果,给他暂存在一个可以直接取到的地方-->内存:缓存

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

什么是缓存【cache】

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

为什么使用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

什么样的数据能使用缓存

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

Mybatis 缓存

Mybatis系统中默认定义了两级缓存,一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启,(Sqlsession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
  • 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次绘画期间查询到的数据会放到本地缓存中
  • 以后如果需要获取想通过的数据,直接从缓存中拿,没必须再去查询数据库

测试步骤

  • 开启日志
  • 测试在一个session中查询两次相同的记录
@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);

    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user==user2);
    sqlSession.close();

}
  • 查看日志输出

缓存失效的情况

  • 查询不同的东西
  • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存
sqlSession.clearCache();//手动清理缓存

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

二级缓存

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

<cache/>
<cache
       eviction="FIFO"
       flushInterval="60000"   60s
       size="512"           最多存512个引用
       readOnly="true"/>   返回的对象被认为是只读的

可用的清楚策略

  • LRU 最近最少使用
  • FIFO 先进先出
  • SOFT 软引用:基于垃圾回收器状态和软引用规则移除对象
  • WEAK 弱引用:更积极的基于垃圾回收器状态和弱引用规则移除对象

默认的清除策略是LRU

二级缓存也叫做全局缓存,

基于namespace级别的缓存,一个名称空间,对应一个二级缓存

工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
  • 如果当前会话关闭了,这个会话的一级缓存就没有了,一级缓存中的数据保存到二级缓存中
  • 新的会话查询信息,就可以从二级缓存中获取内容
  • 不同的mapper查询的数据会放在自己对应的缓存(map)中

步骤

  • 开启全局缓存,设置文件中
<setting name="cacheEnabled" value="true"/>
  • 在要使用二级缓存的mapper中开启,也可以自定义参数
<cache/>
    <select id="queryUserById" parameterType="_int" resultType="User" useCache="true">
        select * from mybatis.user where id = #{id}
    </select>
  • 测试

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

    • public class User implements Serializable {
      

小结:

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

自定义缓存Ehcache

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

要在程序中使用,先导包

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

mapper中设置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

创建文件ehcache.xml

posted @ 2020-10-09 22:18  yourText  阅读(86)  评论(0编辑  收藏  举报