文章目录

  • 一、简介
    • 1.1、什么是Mybatis
    • 1.2、持久化
    • 1.3、为什么需要Mybatis
  • 二、第一个Mybatis程序
    • 2.1、搭建环境
    • 2.2、创建一个子模块
    • 2.3、编写代码
    • 2.4、测试
    • 2.5、小结
  • 三、CRUD
    • 3.1、Mybatis传递多个参数(万能的Map)
  • 四、配置解析
    • 4.1、核心配置文件
    • 4.2、环境变量(environments)
    • 4.3、属性
    • 4.4、类型别名(typeAliases)
    • 4.5、设置
    • 4.6、映射器(mappers)
    • 4.7、其他
  • 五、生命周期和作用域
    • 5.1、SqlSessionFactoryBuilder
    • 5.2、SqlSessionFactory
    • 5.3、SqlSession
    • 5.4、Mybatis三大对象映像图
  • 六、解决属性名和字段名不一致问题
    • 6.1、解决方法一:取别名(数据库层面)
    • 6.2、解决方式二:结果映射resultMap(Java层面,重点)
    • 6.3、用例子明白resultMap底层是反射执行
  • 七、日志
    • 理解打包后的项目经历了什么
  • 八、分页
    • 7.1、使用Limit分页
  • 九、使用注解开发
    • 9.1、面向接口编程
    • 9.2、使用注解开发
    • 9.3、CRUD
    • 9.4、Lombok
  • 十、多对一(association)
    • 解法一:按照结果嵌套查询(连表查询,重要需掌握)
    • 解法二:按照查询嵌套处理(子查询,复杂不建议)
  • 十一、一对多(collection)
    • 小结
  • 十二、动态SQL
    • 动态SQL之我见
  • 十三、缓存
    • Mybatis缓存
    • Mybatis一级缓存
    • Mybatis二级缓存
    • 自定义缓存
    • 缓存之我见

Mybatis

所需环境 回顾
JDK1.8 JDBC
Mysql5.7/8.0 Mysql
maven3.6.1 Maven
IDEA Junit

SSM(Spring + SpringMvc + Mybatis)框架:配置文件相关的。学习的最好方式:官方文档

一、简介

1.1、什么是Mybatis

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

获得Mybatis

  • maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version>
</dependency>
  • 中文文档:https://mybatis.org/mybatis-3/zh/index.html Mybatis学习最重要的文档
  • Github:https://github.com/mybatis/mybatis-3/releases

1.2、持久化

数据持久化

  • 持久化就是将程序的数据在持久状态(硬盘/外存中)和瞬时状态(内存中)转化的过程

  • 内存(Random Access Memory,RAM):断电即失

  在大学里面看过一本关于早期工业革命历史的书,学习到有个叫继电器的器件,早期内存核心就是继电器,具体构造是个弹片,随着电位信息拨动(挨上就是1,没挨上就是0,这也就是为什么计算器底层是二进制的又一证据),这个继电器的电位信息来自于地址总线(几根就是我们说的32/64操作系统,说的就是内存大小),因为直接由地址总线控制,所以能直接定位(Access)某个位置,这也就是为什么内存速度快。同时内存核心元件是继电器,一旦断电,继电器拨片会立马回弹,导致回到初始状态,即数据都变成000000序列了,数据就不复存在了。这就是RAM存储器的特性,速度快,怕断电。在计算机中充当着内存的角色。毕竟是课外学习到的知识,也不是科班,所以可能有误,望高人指出。

  • 数据库(JDBC), io文件持久化。

  本质上都是放在外存上。学习的课外知识。分为固态硬盘和机械硬盘。机械硬盘,你可以看作小时候看的碟机的CD运行原理,有一个小小的指针放在上面随着转动这就是再读取了。所以机械硬盘怕摔,针头偏移了的话,没咬合住就读取不了了。固态硬盘,半导体技术,懵逼,不懂,只知道他有个专业术语是颗粒。

为什么要持久化

  • 假如做一个学生信息管理系统,我们从view层获得一个学生信息,一个老师早上忙活大半天录了300个学生,中午吃饭去电脑断电,在内存(RAM)中的数据,断电丢失,全部没有了,白忙活一场。所以我们要把它转到外存去,长期保存,转移到数据库里,看表容易多了。
  • 内存太贵了

持久层:Dao层,Service层,Controller层。

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

1.3、为什么需要Mybatis

  • 方便帮助程序员将数据存入到数据库中
  • 传统的JDBC代码太复杂了,简化,框架,自动化。
  • 不用Mybatis也可以,学了更容易上手。技术没有高低之分
  • 优点
    • 简单易学
    • 灵活
    • sql和代码的分离,提高了可维护性。
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

总结及自己的思考

  Mybatis框架完全可以不学,你可以用更加全自动的Hibernate框架。Hibernate全自动框架,Mybatis半自动框架。理解就在于hibernate将sql语句都内部处理了(黑箱操作),用户使用时只需调用相关api即可。而mybatis还需自己写sql语句,这样更加灵活。因为不同的需求可能需要不同程度的sql语句,sql这种变化大的由外面来写最好。

  还是那句话,框架没有好坏之分,但现在主流的方向就是这个,所以为了迎合,必须要学。

  同时,对于框架,我只想说,不用太放在心上,刨根问题,就是人家为我们做好的工具,你可以拿来使用,只不过要学习人家规定的语法/配置文件。你完全可以给一个不懂底层实现的人让他去学框架的使用,使用框架使得开发越来越快。框架就是人家定好的规则,你去学习使用完成自己的目标,做一个API的调用者,很简单;但要做一个框架的开发者(工具的建造者),很难。所以学习框架就是个上山和下山的过程,上山学习如何快速使用,下山,刨根问题,去探究人家这个功能是怎么实现的。深度和广度的问题,取决在于你。

二、第一个Mybatis程序

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

2.1、搭建环境

搭建数据库

CREATE DATABASE `mybatis`;USE mybatis;CREATE TABLE `user`(`id` INT(20) NOT NULL,`name` VARCHAR(30) DEFAULT NULL,`pwd` VARCHAR(30) DEFAULT NULL,PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;INSERT INTO `user` (`id`,`name`,`pwd`) VALUES
(1,'狂神','123456'),
(2,'张三','456852'),
(3,'李四','qwertyuiop');

新建项目

  1. 新建一个普通的maven项目

  2. 删除src目录(使用父工程子项目模式来做,为了一呼百应)

  3. 父工程pom.xml导入maven依赖

    <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies>

2.2、创建一个子模块

创建一个子模块,该子模块也是maven的普通项目。

  • 编写mybatis的核心配置文件

在子模块的resource目录(web学习中明白打包导出后直接就在当前目录下)下编写mybatis-config.xml(官方建议这样命名)。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//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.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=false"/><property name="username" value="root"/><property name="password" value="456852"/></dataSource></environment></environments></configuration>

该配置文件不要写中文注释(硬要写UTF-8改为UTF8/GBK)!

&amp;代替&

  • 编写mybatis工具类
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;//本质上在 sqlSessionFactory 建造 sqlSession
public class MybatisUtil {private static SqlSessionFactory sqlSessionFactory;static {try {//使用MybatisUtils第一步:获取sqlSessionFactory对象String resource = "mybatis-config.xml";InputStream resourceAsStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);} catch (IOException e) {e.printStackTrace();}}//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。public static SqlSession getSqlSession() {return sqlSessionFactory.openSession();}
}

2.3、编写代码

  • 实体类pojo

写在对应包下,与数据库表一一对应。

public class UserModel {private int id;private String name;private String pwd;//get,set,toString来一套
}
  • DAO接口
public interface IUserDao {List<UserModel> getUserList();
}
  • 接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.kuang.dao.IUserDao"><select id="getUserList" resultType="com.kuang.pojo.UserModel">SELECT * FROM mybatis.`user`</select>
</mapper>

右边感叹警告,未配置数据库。配置Mysql,方便快速查询相应表和字段。

  • 去mybatis的核心配置文件进行配置
 <mappers><mapper resource="com/kuang/dao/userDaoMapper.xml"/></mappers>

因为此时要访问的是一个目录下的xml文件,而不是类文件,所以要用/,而不用.

2.4、测试

junit测试,应该去test目录下进行。所以要在test目录下建立和src一样的包结构

import com.kuang.pojo.UserModel;
import com.kuang.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;import java.util.List;public class userDaoTest {@Testpublic void userDaoTest() {//第一步:获得SqlSession对象SqlSession sqlSession = MybatisUtil.getSqlSession();//执行IUserDao userDao = sqlSession.getMapper(IUserDao.class);List<UserModel> userList = userDao.getUserList();for (UserModel userModel : userList) {System.out.println(userModel);}sqlSession.close();}
}

可能遇到的问题

  1. 配置文件没有注册

要去mybatis的核心配置文件下进行相关xml文件的注册绑定

  1. 绑定接口错误(namespace)
  2. 方法名不对(id)
  3. 返回类型不对(resultType)

  1. Maven导出资源问题

Mybatis为了增加关联性,我们经常把xml文件等配置文件和类放在一起。但是默认导入包不会导出src下的配置文件。所以在父工程下的pom.xml下加过滤器。

为了关联性:

为了不被过滤,所以需要在pom.xml添加

    <build><resources><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources></build>

2.5、小结

  Mybatis就是把我们以前写userDaoImple实现类做法转成了写一个xml文件的做法。让我们以前写的好多代码(connection、preparestatment等)得到简化。

演化过程

以前做法 Mybatis做法
implements 接口 namespace = “包权限接口名”
实现接口方法 id=“接口方法名”
返回值 resultType = “带包权限类名”

  Mybatis就是把实现类变为写一个xml标签,其标签体专注SQL语句。写完该xml,记得要去mybatis的核心配置文件进行注册namespace是绑定,去核心配置文件添加叫注册

三、CRUD

  • id:就是对应的namespace中的方法名;
  • resultType:SQL语句的返回值!
  • parameterType:参数类型。

有了Mybatis以后的CRUD步骤都是一致的。

编写接口—>编写接口对应的mappeer.xml中的sql语句—>测试(固定四步,打开sqlSession;获取sqlSession.getMapper(接口.class);进行相关操作;最后关闭sqlSession)

Select

选择,查询语句;

  1. 编写接口
    //名字命名起的越底层,高层越方便调用以达到复用,DAO和Service想成两个人之间的对话UserModel getUserRecordById(int id);
  1. 编写对应的mappeer中的sql语句
    <select id="getUserRecordById" parameterType="int" resultType="com.kuang.pojo.UserModel">SELECT * FROM mybatis.`user` where id = #{id}</select>
  1. 测试
    @Testpublic void selectTest() {SqlSession sqlSession = MybatisUtil.getSqlSession();IUserDao userDao = sqlSession.getMapper(IUserDao.class);System.out.println(userDao.getUserRecordById(2));sqlSession.close();}

Insert

    <!--对象中的属性可以直接取出来--><insert id="addUserRecord" parameterType="com.kuang.pojo.UserModel">insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})</insert>

Update

    <update id="updateUserRecord" parameterType="com.kuang.pojo.UserModel">update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}</update>

Delete

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

需要注意的是:增删改操作必须进行业务提交sqlSession.commit();

期间发现几个问题。

  1. 参数名称为一个时可以任意命名,id换成goushi,任意都行,两边驴唇不对马嘴都可以,这应该和mybatis底层有关,到时候深入研究。(后面万能的map学到了,单个参数都可以不用写,自动匹配到形参)
  2. 增删改这几个操作,返回值可以为int(但此int不是受影响行行数),可以为boolean,我不理解它返回值为什么可以有两个,应该和底层有关,学习到再说。

有可能错误的原因

  • 标签要匹配。update标签是UPDATE的SQL语句使用的,delete标签是DELETE的SQL语句使用的。
  • resoure绑定mapper路径要用/链接。前面说过了要访问的是一个目录下的xml文件,而不是类文件,所以要用/,而不用.

3.1、Mybatis传递多个参数(万能的Map)

回顾上面写的,修改(update)用户时,不应该传一个user,而应该传递id(改哪个),name、pwd(改成什么);或者考虑这么一个问题,如果一个实体类有100个字段值,你填都要填半天,我们只填自己需要的。

IUserDao

    //重名了,测试看可以吗?  不可以,报错,Mapped Statements collection already contains//map映射已经有一个了,也就是说方法的重载在mybatis是不可行的//boolean updateUserRecord(Map<String,Object> map);boolean updateUserRecordNew(Map<String,Object> map);

userDaoMapper.xml

    <update id="updateUserRecordNew">#这里的 updateName updatePwd whichId 必须和塞的时候放的键值一模一样update mybatis.user set name = #{updateName},pwd = #{updatePwd} where id = #{whichId}</update>

测试

 @Testpublic void newUpdate() {SqlSession sqlSession = MybatisUtil.getSqlSession();IUserDao userDao = sqlSession.getMapper(IUserDao.class);Map<String, Object> map = new HashMap<>();map.put("whichId",0);map.put("updateName","王小明");map.put("updatePwd","mzby");//#这里的 塞的键值updateName updatePwd whichId 必须和xml取得时候保持一致//否则执行时返回的为0/false,且数据库没发生变化System.out.println("成功了吗?" + userDao.updateUserRecordNew(map));sqlSession.commit();sqlSession.close();}

因此,建议Mybatis在传递多个参数时,使用map(设置 parameterType=“map”),直接在sql中取出key即可!

可以但不建议传输对象实例,做法(设置parameterType=“带包权限类名”),直接在sql中取出对象的属性即可!

只有一个基本类型参数的情况下,可以自己在sql中取到,且不管形参名字!底层原因好像就是以下标取得的。

多个参数用Map,或者注解!

其他多参传递参考这个博文 即可,但最简单最直观还是建议使用Map(官方也是这么说的)。

经过这次例子,还学到了Mybatis中方法不可以重载,因为你是通过名字绑定的,重名问题解决不了。从这回想起方法重名(重载)的底层原理,我们看到的方法名其实在底层根本不是这个,而是带数字的,形如fun_1(),fun_2()之类的,不过程序员这层看它们名字相同。

模糊查找

  1. Java代码执行的时候,传递通配符% %
List<User> userList = mapper.getUserLike("%李%");
  1. 在sql拼接中使用通配符!
select * from mybatis.user where name like "%"#{value}"%"

四、配置解析

4.1、核心配置文件

  • mybatis-config.xml(官方建议这个命名,放在resources目录下,随着java代码一起导出在target里)
  • MyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

4.2、环境变量(environments)

MyBatis 可以配置成适应多种环境。这种机制有助于将 SQL 映射应用于多种数据库(Mysql,Oracle)之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置。

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

学会使用配置多套运行环境

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

     <environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><!--....--> </dataSource></environment>

4.3、属性

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

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

db.properties 放在resources目录下,打包后直接在classpath下

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username=root
password=456852

在核心配置文件中引用

    <!--核心配置文件中引入,下面数据库连接${}就可以用--><properties resource="db.properties"><!--还可以直接添加一些属性--><property name="usernameOne" value="root"/><property name="password" value="qwert"/></properties>
  • 可以直接引入外部文件
  • 可以再其中增加一些属性配置(property)
  • 如果有外部配置和xml的property标签都有的同一字段,会优先使用外部配置文件的

并且,从这个问题从中学到了,XML文件标签是有结构顺序的!!!

可以看到,如果相关XML前后位置写错,IDEA也会报错的。

你如果深入到底层去看XMLConfigBuilder这个类parseConfiguration方法,可以看到它解析就是有顺序的

private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}

4.4、类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。
  • 意在降低冗余的全限定类名书写
<typeAliases><typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>

在实际开发中,工作项目下,还是会使用完全限定名,使得以后一眼就能看明白!

4.5、设置

还是都参考官方文档。

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

最为重要的四个参数,要眼熟。

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

驼峰转换有时候需开启,我们知道数据库字段名一般都用下划线隔开,因为数据库对大小写不敏感,而且Oracle数据库都是全大写,所以要用下划线隔开,而实体类中的属性名是以驼峰命名法走的,因此两者挂钩需要进行修改。

4.6、映射器(mappers)

前面我们说过,当相关接口的实现的mapper.xml(形如userDaoMapper.xml)写出来后,就立刻去核心配置文件注册它。

方式一:【极其推荐使用】万能,写对绝对可用

    <mappers><mapper resource="com/kuang/dao/userDaoMapper.xml"/></mappers>

我们知道此时是根据路径去定位相关文件,因此这里用的是/而不是.。或者确切的来说,只有Java的目录结构用.来进行深入访问,而其他所有文件都和操作系统保持一致。

方式二:【有额外要求,不推荐使用】使用class文件绑定注册

    <mappers><mapper class="com.kuang.dao.UserMapper"></mapper></mappers>
  • 接口和他的Mapper配置文件必须同名
  • 接口和他的Mapper配置文件必须在同一个包下

4.7、其他

都参考官方文档。

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plusings插件
    • mybatis-generator-core
    • mybatis-plus
    • 通用mapper

五、生命周期和作用域

这一节,我想拿出来单独说,因为里面包含的是设计思想。

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

5.1、SqlSessionFactoryBuilder

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

可以深入到源码去看,SqlSessionFactoryBuilder最主要都是为外提供了build方法,都是为了得到一个SqlSessionFactory。而这些build方法,其本质都是在解析核心配置XML文件!

可以这样理解:其作用读取XML文件,根据XML(environment标签)实例出不同的SqlSessionFactory,SqlSessionFactoryBuilder就是读取核心配置文件产生SqlSessionFactory,而我们知道有个environment标签,会产生不同的环境,SqlSessionFactoryBuilder就是根据这个来创造SqlSessionFactory的。

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

5.2、SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。(其随着程序开始开始,程序关闭而结束)
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSessionFactory是一个对应环境(environment标签)的实例,可以理解为数据库连接池,程序运行中一直存在。某个数据库(JDBC/Oracle)的数据库连接池。应该单例,一次程序完整运行,一次环境(某个数据库),随着一次程序的关闭再关闭。单例原因:一次程序运行只能使用一个环境(用一个数据库)。

其源码主要方法都是一个openSession(),从这可以更加确定的认为其是一个数据库连接池,对外提供数据库连接,可提供多个。

5.3、SqlSession

  • 类似Connection和Preparestatment的集合体
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要赶紧关闭,否则资源被占用。

SqlSession可以看作从数据库连接池(SqlSessionFactory)得到的一次连接(具有connection和preparestatment的能力)。线程不安全,用完及时归还。

我们可以看它的源码,SqlSession就是一个接口,其里面含有

void commit();
void rollback();
Connection getConnection();
<T> T getMapper(Class<T> type); //mybatis最主要用这个

从上述看来,它就是Connection和Preparestatment的集合体。

5.4、Mybatis三大对象映像图

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

Java采用驼峰命名原则,而数据采用下划线分割命名原则,这两肯定会有不一致的情况。

例如,如下情况。

数据库中的字段

实体类

public class UserModel {private int id;private String name;private String password;
}

测试出现问题

仔细查询,明白问题所在,在相关mapper的xml文件中的SQL语句

select * from mybatis.user where id = #{id}
//同等于
select id,name,pwd from mybatis.user where id = #{id}

*是选中数据库的所有字段名,所以选的是pwd,而此时实体类属性名为password,两者不一致,所以导致没查出来(后面分析很明显这是使用了反射机制)。

6.1、解决方法一:取别名(数据库层面)

 select id,name,pwd as password from mybatis.user where id = #{id}

只需要构造sql语句时将它的别名和属性名一致即可。分析其原因:其实就是sql执行后会建立一张新表,这个表的名字就是按上面走的,然后这时候就一致了(就方便反射了)。

这种解决方式太low了,取巧方式,如果有很多字段和属性不一致就要大量增加。Mybatis有自己的解决方式。

6.2、解决方式二:结果映射resultMap(Java层面,重点)

属性名:id name password
字段名:id name pwd

在相关mapper的XML里将这种映射关系描述出来,在一个mapper标签里里表述出来

<mapper namespace="com.kuang.dao.IUserDao"><resultMap id="resultMapOne" type="com.kuang.pojo.UserModel"><result property="id" column="id"/><result property="name" column="name"/><result property="password" column="pwd"/></resultMap><select id="getUserList" resultMap="resultMapOne">select * from mybatis.user</select>
</mapper>

其实相同的可以删除。可以将id和name的映射删除也不影响结果。

6.3、用例子明白resultMap底层是反射执行

  说实话看到这个resultMap,我有很亲切的感觉,我做过相关的工作(详见自主实现简易的ORM框架)。这个resultMap这种机制我亲手实现过,其追其根本就是反射机制

当我看到mybatis和我以前的做法一样时,不禁狂喜,但是应该怎么测出来呢,得出它使用了反射机制这个结论呢?是个博弈猜测对话的过程,在此分享出来。

一开始,我就把实体类的所有gettersetter方法全部删除,我没有反射执行的方法,我看你怎么反射。

(1)删除所有getsetter,没有我看你怎么反射

public class UserModel {private int id;private String name;private String password;}

但是,结果还是出来了。从这可以分析出,mybatis框架的设计者考虑到有这种情况,没有相关set方法就自己创建相关set方法。佩服!那继续吃我下一招。

(2)getseter设置为private,定义了设置为私有的我看你怎么反射

很遗憾的是,这次mybatis框架设计者又赢了。依然可以执行出结果。从这明白了,反射存在setAccessable这种黑科技,priavte修饰符在这都没用。

(3)实属无奈,最后想到小黄鸭调试法

直接给setgettet方法里面加输出语句,看他是否输出,从此判断是否使用了反射机制。

public class UserModel {private int id;private String name;private String password;public int getId() {return id;}public void setId(int id) {System.out.println("setId反射执行了");this.id = id;}public String getName() {return name;}public void setName(String name) {System.out.println("setName反射执行了");this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {System.out.println("setPassword反射执行了");this.password = password;}
}

答案是令人满意的,确实输出了,也直接证明了resultMap是使用反射机制实现的,和以前的我做的ORM框架类似。

这只是resultMap的基本用法,用来映射简单的字段和属性。但我们知道一个数据库一定是复杂的,类的属性也不简单的是八大基本类型,肯定有类类型。所以resultMap后面引入了association等其他标签,这就是resultMap的高级用法了。但是,我只想究其本质的说,根本不需要怕什么高级用法,这个XML的resultMap的标签只是在描述表和类之间的不同的部分,心中有了映像图,不论是写还是读,你都可以很快明白association就是描述来自于其他表的字段和属性之间的映射关系,追溯其里面的标签还是在描述字段和属性的映射关系,我们此时就可以和我做的框架一样把这种映射关系提取出来形成一个类PropertyColumnDefintion,这就是我的ORM框架的做法了,我不知道Mybatis底层是怎样做的,但我认为我这方便理解面向对象思想。

总结:mybatis对于属性和字段名不一样有两种解法。这两者分别就是数据库层面上和Java层面上,对于数据库层面上的修改可以通过别名解决,而在Java层面上就要借助反射机制了。

同时,追求解答的过程是有趣并痛苦的。从这可以看出来Mybatis框架鲁棒性是多么的强啊!膜拜

七、日志

如果数据库操作,出现异常,我们需要排错。日志就是最好的助手!

曾经:sout(小黄鸭测试法)、debug

现在:日志工厂。

  • LOG4J【掌握】
  • STDOUT_LOGGING 【掌握】

在Mybatis中具体使用哪一个日志实现,在设置中设定!

STDOUT_LOGGING 标准日志输出

在mybatis核心配置文件中,配置日志!

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

Log4j

什么是Log4j

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. pom下先导入log4j包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
  1. log4j的相关配置,放在resources下 log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和fiLe的定义在下面的代码
1og4j.rootLogger=DEBUG, console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
1og4j.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
1og4j.appender.file.File=./log/kuang.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.sq1=DEBUG
1og4j.logger.java.sq1.statement=DEBUG
log4j.logger.java.sq1.ResultSet=DEBUG
log4j.logger.java.sq1.PreparedStatement=DEBUG

3 . 配置log4j为日志的实现

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

Log4j类中使用

  1. 在要使用Log4j的类中,导入包import org.apache.log4j.Logger;

  2. 日志对象,参数为当前类的class

static Logger logger = Logger.getLogger(UserDaoTest.class);
  1. 日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");

理解打包后的项目经历了什么

  在日志学习这里遇到找不到相关资源问题,经过网上搜索解决了,并且深刻的理解,打包后的项目都发生了什么?

  首先,一个Mavne项目的目录结构从src开始分析,分为两大部分main和test。main下的java(蓝色标注):该目录下是我们写的.java后缀结尾的源代码,resources(黄色金币标注):该目录下是我们项目所需要的xml、properties文件。test下就是测试的东西,需要程序员自己进行和main形成一个镜像。

  一旦Maven项目运行起来了,它就进行了打包。main和test分别形成了classes和test-classes目录。classes目录展开来看和java目录有着一模一样的结构,文件也都一样只不过名称变成了.class的字节码文件。我们以后为了保护源码传给别人的就是打包后的classes文件;同时,还发现并没有resources目录了,而原本resources目录下的所有文件直接附着在了classes文件下,这就是为什么我们经过打包后,每次访问资源文件都不需要加路径,直接进行访问,因为程序当前就在classes目录下待着,它此时就是以这里为根据,访问其他相对位置的地方,这个初始的路径我们有个重要的称谓classPath,classPath就是我们程序员编写代码前最开始的路径,也是打包后的class路径,resources目录随着打包自动合并了。Test也是如此。

InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//这就是为什么我们直接可以访问资源文件前不需要加任何路径的原因,因为当前就在classPath下
//以后所有去访问所有路径,都站在该classPath进行相对定位Thread.currentThread().getContextClassLoader().getResource("")
//而这句话,根据classLoad就能得到当前程序运行的绝对路径(带盘符的那种),就知道当前在哪个物理上真真实实的路径了

所以我们,以后只提classPath这个概念。普通Java程序的src就是classPath,web项目的src也是classPath,只要开始编代码的都是classPath,resources目录随着打包自动合并,其他路径的文件根据当前classPath进行相对定位!

八、分页

思考:为什么要分页?

  • 减少数据的处理量
  • 一个页面显示不下

7.1、使用Limit分页

分页SQL语句 limit

select * from mybatis.user limit startIndex,pageSize;
#从第几个下标(startIndex)开始,显示几个(pageSize)
select * from mybatis.user limit 3;
#[0,3]

使用Mybatis实现分页,核心SQL

1.接口

List<UserModel> getUserListLimit(Map<String, Integer> paraMap);

2.绑定一个mapper.xml

<mapper namespace="com.kuang.dao.IUserDaoFour"><resultMap id="getUserListLimitPropertyColumnMapping" type="com.kuang.pojo.UserModel"><result property="password" column="pwd"/></resultMap><select id="getUserListLimit" parameterType="map" resultMap="getUserListLimitPropertyColumnMapping">select * from mybatis.user limit  #{startIndex},#{pageSize}</select>
</mapper>

3.测试

    public void test() {SqlSession sqlSession = MybatisUtil.getSqlSession();Map<String,Integer> paraMap = new HashMap<>();paraMap.put("startIndex", 0);paraMap.put("pageSize", 3);IUserDaoFour iUserDaoFour = sqlSession.getMapper(IUserDaoFour.class);List<UserModel> userListLimit = iUserDaoFour.getUserListLimit(paraMap);for (UserModel userModel : userListLimit) {System.out.println(userModel);}sqlSession.commit();sqlSession.close();}

RowBounds分页

过时了,不建议学习。

分页插件

pageHelper

九、使用注解开发

9.1、面向接口编程

-大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
-根本原因∶解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
-在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
-而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

-接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

-接口的本身反映了系统设计人员对系统的抽象理解。
-接口应有两类:
-第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);-

-第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface) ;-

-一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

-面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法.

-面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现.

-接口设计与非接口设计是针对复用技术而言的,与面向对象〈过程)不是一个问题.更多的体现就是对系统整体的架构

我理解的面向接口编程:接口是由富有经验的架构师设计的,一个优秀的架构师可以设计十分实用且复用次数多的框架。面向接口编程在工程上就是为了省钱,加快开发效率。试问有了Java之后,你给一个学校开发一套系统,完成了然后再给另一个学校开发他们的系统,难道还需要从头开始吗?这是不必要的,因此有了框架的概念,框架相当于人的骨头,肉随着骨头去长,也就是说其他锦上添花的需求不应该在框架内考虑,框架只做主体系统的内部结构。框架按层结构来搭建,Dao层只干Dao层的事,Service只做自己该干的事。这一层的接口只做自己接口的事。

对接口有了更深刻的思考,即插即用,我只是定个方向,具体怎么实现接口不管,你给我注入不同的实现类,该接口就可以进行复用了。

9.2、使用注解开发

  1. 注解在接口上实现(接口实现了impl,就一个简单SQL)
    @Select("select * from mybatis.user")List<UserModel> getUserList();
  1. 需要在核心配置文件中绑定接口!
    <mappers><!--<mapper resource="com/kuang/dao/UserDaoFourMapper.xml"></mapper>--><mapper class="com.kuang.dao.IUserDaoFour"/><!--  Resource绑定时使用 / 访问文件,class就用.访问java文件  --></mappers>
  1. 测试

本质:反射机制和动态代理机制

9.3、CRUD

编写接口,增加注解

@Select("select * from user")
List<User> getUsers();//方法存在多个参数,所有的参数前面必须加@Param("id")注解
@Select("select * from user where id = ${id}")
User getUserById(@Param("id") int id);@Insert("insert into 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);

测试类

【注意,必须将接口注册绑定到核心配置文件中】

@Test
public void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();IUserDaoFour mapper = sqlSession.getMapper(UserMapper.class);mapper.updateUser(new User(98, "sino", "abcdef"));sqlSession.commit();//不加,数据库不会有sqlSession.close();
}

关于@Param("")注解

  • 基本类型的参数或者String类型,需要加上 快一点
  • 引用类型不需要加(类类型映射不到)
  • 如果只有一个基本类型的话,可以忽略(底层根据下标找的),建议加上。
  • 在SQL中引用的就是@Param(“uid”)中设定的属性名

#{}(防止SQL注入), ${}(可以拼接,导致SQL注入)

9.4、Lombok

没有进行学习,getsetter写起来很快的,没必要让别人帮你做。同时getsetter才是一个面向对象的灵魂(封装思想)所在!而且,如果你想要自己千奇百怪的pojo输出格式,就需要自己写toString方法,别什么都依赖别人帮你写。

十、多对一(association)

ER图

多对一:

  • 多个学生,对应一个老师
  • 对于学生这边而言, 关联, 多个学生,关联一个老师【多对一】
  • 对于老师而言,集合, 一个老师有很多学生【一对多】

数据库图

所需要的数据库:

CREATE TABLE `teacher`(`id` INT(20) NOT NULL,`name` VARCHAR(30) DEFAULT NULL,PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;INSERT INTO `teacher`(`id`,`name`) VALUES
(1,'朱老师');
INSERT INTO `teacher`(`id`,`name`) VALUES
(2,'狂老师');CREATE TABLE `student`(`id` INT(20) NOT NULL,`name` VARCHAR(30) DEFAULT NULL,`teacher_id` INT(10) DEFAULT NULL,PRIMARY KEY(`id`),KEY `fktid` (`teacher_id`),CONSTRAINT `fktid` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(1,'小明',1);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(2,'小红',2);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(3,'小张',1);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(4,'小李',2);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(5,'小王',1);

建议一步步执行,否则会出现表还没建立,就先插入数据导致错误了。

工程上还是建议不要建立外键,这里只是为了举例子才设置外键。

建立实体类

TeacherModel

public class TeacherModel {private int id;private String name;//getset toString来一套
}

StudentModel

public class StudentModel {private int id;private String name;//private int teacherId; 考虑再三,不需要private TeacherModel teacherModel;//get set toString
}

这个teacherId,到底在不在StudenModel里有以下的考虑。

teacherId存在:

优点:以后得到一个学生的实例(studentmodel),可以直接根据它蕴藏的tid查到它的老师(后面发现好像teacher就表示了,teacherId真的没有存在必要了,立刻删)

缺点:数据冗余,teacherId与teacher表达意思一样,数据冗余。

在java的世界里,teacher就是teacher,它也是个实体类,我根本不管你在数据库的编号是啥,我只只知道student有teacher这个属性。所以 int teacherId没必要。 或者是,可以写这个teacherId,不过不对外显示,get方法包权限,set方法包权限,只在内部用,对外不显示。

从这明白了,java是java的世界,数据库是数据库的世界,我们俩要的不一样,表示也就不一样。

建立接口

public interface IStudentDao {List<StudentModel> getStudentList();
}

建立接口的实现XML文件

解法一:按照结果嵌套查询(连表查询,重要需掌握)

StudentDaoMapper.xml

<mapper namespace="com.kuang.dao.IStudentDao"><select id="getStudentList" resultMap="getStudentListPropertyColumnMapping">select s.id as sid,s.name as sname,s.teacher_id as tid,t.name as tnamefrom student as s,teacher as t where s.teacher_id = t.id</select><resultMap id="getStudentListPropertyColumnMapping" type="com.kuang.pojo.StudentModel"><!--只写当前表与pojo类属性不同--><result property="id" column="sid"/><result property="name" column="sname"/><!--复杂的属性需要association再深入--><association property="teacherModel" javaType="com.kuang.pojo.TeacherModel"><result property="id" column="tid"/><!--没有这句话,teacher注入不完整--><result property="name" column="tname"/></association></resultMap></mapper>

这里最重要的就是写SQL语句了,在这我也彻底明白了SQL语句,下面总结下。

select语句主体

SELECT (查出来形成新表的列名,强烈建议用as写好新表的列名) FROM (从哪几张表,建议也用as起别名,方便区分是哪一个表的列) WHERE (条件,不加就会变成笛卡尔积的形式)

写Select语句就先写主体,SELECT … FROM … WHERE,然后根据需求去填充。(别名用as明示,别只写个空格)

select s.id as sid,s.name as sname,s.teacher_id as tid,t.name as tname
from student as s,teacher as t where s.teacher_id = t.id

联表查询的SQL语句就很好写和分析了。上面的SQL语句代表从student表(别名为s)和teacher表(别名为t),形成结果新表sid列是student表的id;新表sname列是student表的name;新表tid列是student表的teacher_id;新表tname是teacher表的name;条件为student表的teacher_id与teacher表的id相等。

起别名的原因是:经过select查询形成的新表的新列名就是按照别名走的,如果没设置就按源表走;同时设置了别名,mybtatis写参数也就按照新表走了。

在mybatis核心配置XML文件进行注册

    <mappers><mapper resource="com/kuang/dao/StudentDaoMapper.xml"/></mappers>

测试,成功。

解法二:按照查询嵌套处理(子查询,复杂不建议)

  1. 查询所有学生的信息
  2. 根据查询出来的学生的tid,寻找对应的老师! 子查询

StudentDaoMapper.xml

    <select id="getStudentListOne" resultMap="getStudentListOnePropertyColumnMapping">select * from student# 直接查全表</select><resultMap id="getStudentListOnePropertyColumnMapping" type="com.kuang.pojo.StudentModel"><association property="teacherModel" column="teacher_id" javaType="com.kuang.pojo.TeacherModel" select="exSelect"/><!--  再多个查询  --></resultMap><select id="exSelect" resultType="com.kuang.pojo.TeacherModel">select * from teacher where id = #{id}</select>

在mybatis核心配置XML文件进行注册;测试,也成功。

但还是建议使用连表查询,构造SQL简单且易懂。

连表查询构建的SQL直接可以在数据库的控制台进行输入,然后看结果,而子查询不行,子查询还牵扯参数传递,所以复杂不好理解和调试。

十一、一对多(collection)

比如:一个老师拥有多个学生!

对于老师而言,就是一对多的关系

实体类

public class TeacherModel {private int id;private String name;private List<StudentModel> studentList;
}

接口

public interface ITeacherDao {TeacherModel getTeacherById(@Param("teacherId") int id);
}

建立接口的实现XML文件

TeacherDaoMapper.xml

<mapper namespace="com.kuang.dao.ITeacherDao"><select id="getTeacherById" resultMap="getTeacherByIdPropertyColumnMapping">SELECT s.id AS sid, s.name AS sname, t.name AS tname, t.id AS tidFROM student AS s, teacher AS tWHERE s.teacher_id = t.id AND t.id = #{teacherId}</select><resultMap id="getTeacherByIdPropertyColumnMapping" type="com.kuang.pojo.TeacherModel"><result property="id" column="tid"/><result property="name" column="tname"/><collection property="studentList" ofType="com.kuang.pojo.StudentModel"><result property="id" column="sid"></result><result property="name" column="sname"></result></collection></resultMap></mapper>

去mybatis核心配置文件进行注册,测试ok。

只写了连表查询,子查询就不写了,难度大且不易懂。

小结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. javaType:用来指定实体类中的属性的类型
  4. ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!

注意点:

  • 保证SQL的可读性,进来保证通俗易懂
  • 注意一对多和多对一,属性名和字段的问题!
  • 如果问题不好排查错误,可以使用日志

慢SQL 1s 1000s的差距 学习了sql索引知识后,把我们现在写的sql放在一边,参照着写。

十二、动态SQL

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

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>

where 1 = 1这个是为了where语句必须出现,但不知道后面是否满足条件。因此写个1 = 1。1=1 永真, 1<>1 永假。where 1<>1 这句查询出来的是 只要表结构不要表数据。

choose (when, otherwise)形如switch语句

<select id="queryBlogIF" 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>

这里没有再使用where 1 = 1了,而使用了where标签。where标签,可以让SQL字符串拼接正确。

有了where标签就不建议使用where 1=1了。where 1=1会导致表中的数据索引失效。

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

SQL片段和foreach操作就不涉及了。

动态SQL之我见

怎么说呢,我认为理解并且会写最简单if就行了,动态SQL就是在拼接SQL字符串,mybatis就是操作数据库的框架,按照分离原则,数据库只管数据库的操作,少引入逻辑操作,引入个if就不错了。没有动态SQL,你可以一直划分细颗粒度的DAO操作,写的越简单越基础的方法,粒度越小,复用性越高,可以称为元操作吧。

十三、缓存

  缓存(Cache,kashi,以前一直读错kachi纠正),这是一个很重要的概念。缓存对mybatis优化很起作用,缓存说白了就是提高查询效率。

  最开始,单服务器,少量用户向服务器进行请求资源,服务器只负责和客户连接,资源文件肯定不存在服务器上,而放在数据库上,服务器分析客户的请求,然后从数据库取得相关数据再返还,早期就是这样的单线;

  而随着网络越来越发达,用户数量激增,传统的单个服务器承受不了巨大的压力,因此就需要多个服务器来进行处理,这些服务器可以跨地域设置以满足不同地区用户的体验感,快速响应用户请求。

  虽说请求由于多个服务器所以处理速度快了,但是真正的资源要去数据库拿;而数据库其本质也是个服务器,其最主要工作就是读和写。压力有都转移到数据库这里了。应该怎么办呢?架构中没有什么是加一层解决不了的,分析读操作和写操作应该是独立的,所以加一层memoryCache缓存,memoryCache存储常用的数据库资源,实现读写分离,读操作先读memoryCache读,没有再去数据库读,而写的压力全给数据库。

  如果还嫌处理速度不够快,那么就把数据库也弄成多个,这样可以分担压力。但是多个数据库就要考虑数据一致性的问题了。如何保证呢?主从复制!

从上面发展历史中,你也就能明白缓存就是一个存放常用数据的仓库(池子),方便用户快速得到。

Mybatis中的缓存也是如此意思

1.什么是缓存[ Cache ]

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

2.为什么使用缓存?

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

3.什么样的数据能使用缓存?

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

Mybatis缓存

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

Mybatis一级缓存

mybatis一级缓存又称本地缓存。

  • 一级缓存默认打开

  • 一级缓存生命周期是一次会话(一次打开和关闭sqlSession),此会话期间有效

    public void test() {SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();  //openSession//一级缓存仅在此之间有效sqlSeeion.commit();sqlSeeion.close();}

(1)与数据库同一次会话期间查询到的数据会放在本地缓存中。

(2)以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

  • 例子证明一级缓存存在
    @Testpublic void test() {SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);UserModelFive user = mapper.getUserById(1);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}UserModelFive anotherUser = mapper.getUserById(1);System.out.println("他俩地址值相同吗?" + (user == anotherUser));    //truesqlSeeion.commit();sqlSeeion.close();}

输出为true,查询同一个id,一开始我以为底层肯定是使用反射机制new了一个新的pojo对象,而此时==为true,说明地址值相同,就说明此时这两个对象是一样的,这就证明了一级缓存的存在。同时日志信息可以看到相关SQL只执行了一次,说明第二次对象就是从一级缓存取的。

  • 一级缓存失效的情况

(1)增删改(insert,update,delete)操作,可能会改变原来的数据,为了确保数据实时,所以会刷新一级缓存

(2)查询不同的Mapper.xml(也就是说查了另一个接口的实现XML文件,从这知道同一个sqlsession下的不同mapper的缓存是各自的,后证实为不同的mapper.xml对应不同的二级缓存,后面学到可以mapper.xml通过引用传递二级缓存)

    @Testpublic void testMapperOneCache() {SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();IUserDaoFive mapperOne = sqlSeeion.getMapper(IUserDaoFive.class);UserModelFive userOne = mapperOne.getUserById(1);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}IUserDaoSix mapperTwo = sqlSeeion.getMapper(IUserDaoSix.class);UserModelFive userTwo = mapperTwo.getUserByUserId(1);System.out.println("地址值相同吗?" + (userOne == userTwo));//false。说明不同的mapper.xml有自己不同的一级缓存(后证实为不同的mapper.xml对应不同的二级缓存)sqlSeeion.commit();sqlSeeion.close();}

(3)手动清理缓存

sqlSeeion.clearCache();

Mybatis二级缓存

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

使用步骤

  • 前提条件一:在核心配置文件显示的开启全局缓存
    <settings><setting name="cacheEnabled" value="true"/><setting name="logImpl" value="STDOUT_LOGGING"/></settings>
  • 前提条件二:在当前Mapper.xml中使用二级缓存开启
    <!--当前mapper.xml里开启二级缓存--><!--<cache/>  简单一句话就开启了,下面是还可以加参数--><cacheeviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>
  • 所有的数据都会先放在一级缓存中,只有会话提交(sqlSeeion.commit()),或者关闭(sqlSeeion.close())的时候,才会转存到二级缓存中!

  • 开启上面两个条件后,二级缓存就生效了。当一级缓存关闭(sqlsession进行close后)时,同一mapper.xml下查询的对象会放在二级缓存中;不同的mapper.xml有自己独有的二级缓存。

    @Testpublic void testTwoCache() {SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);UserModelFive user = mapper.getUserById(1);sqlSeeion.commit();sqlSeeion.close();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}SqlSession sqlSeeion1 = MybatisUtil.getSqlSeeion();IUserDaoFive mapper1 = sqlSeeion1.getMapper(IUserDaoFive.class);UserModelFive user1 = mapper1.getUserById(1);sqlSeeion1.commit();sqlSeeion1.close();System.out.println("地址值相同吗?" + (user == user1));//true//1.核心文件开启显示开启缓存    <setting name="cacheEnabled" value="false"/>//2.mapper.xml定义缓存   <cache/>//cache没有readOnly="true",会报未序列化异常,和流有关系}
  • 不同mapper.xml引用别的mapper.xml的缓存
    @Testpublic void testMapperMoveStore() {SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);UserModelFive user = mapper.getUserById(1);sqlSeeion.commit();sqlSeeion.close();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}SqlSession sqlSeeion1 = MybatisUtil.getSqlSeeion();IUserDaoSix mapper1 = sqlSeeion1.getMapper(IUserDaoSix.class);UserModelFive user1 = mapper1.getUserByUserId(1);sqlSeeion1.commit();sqlSeeion1.close();System.out.println("不同mapper的二级缓存可以引用吗?" + (user == user1));//true。探究过程很有意思}
    <!--<cache/>--> <!--不使用自己的二级缓存池,而引用别的mapper.xml所对应接口的二级缓存池--><cache-ref namespace="com.kuang.dao.IUserDaoFive"/>

这里试了半天都是false,心态都崩了,和官方文档说的cache-ref不一样啊。在崩溃之际,网上搜索资料,终于解开谜题。参考了这位大佬的博文cache-ref。从中明白了cache是一个实例对象,不能既引入<cache/>,还引入<cache-ref/>;解析<cache/>结点,Mapper为自己创建一个缓存实例不再引用<cache-ref/>的缓存实例;也就是说一个mapper要么定义自己的二级缓存,要么引用别人的二级缓存,只能用一个池子。

缓存原理映像图

自定义缓存

当然可以使用自己自定义的缓存,我们先来看org.apache.ibatis.cache.Cache接口。

package org.apache.ibatis.cache;import java.util.concurrent.locks.ReadWriteLock;public interface Cache {String getId();void putObject(Object key, Object value);Object getObject(Object key);Object removeObject(Object key);void clear();int getSize();default ReadWriteLock getReadWriteLock() {return null;}}

你要实现自定义缓存就要实现该接口,完成其规定的所有方法。

从这里明白了。实际上,设计一个接口,提出这些方法就是设计一种策略!规定你必须完成什么,后面的实现该接口的实现类是策略的具体实现了。

然后相关mapper.xml导入自定义缓存就行了

<cache type="com.domain.something.MyCache"/>

工作一般在Redis(已经做到极致了)数据库来做缓存! K-V键值对的形式。都以K-V存储,直接取,直接放。这个我们到后面再学习。

缓存之我见

缓存落到最后都是为了提高查询效率。

一级缓存其作用域就是随着sqlSeesion生死,仅在同一个sqlSession包裹下有效,不同的sqlSession有自己不同的一级缓存。

二级缓存前提条件需要进行核心配置文件的显示开启,还需要在mapper.xml下开启cache标签。所有的数据都会先放在一级缓存中,只有会话提交(sqlSeeion.commit()),或者关闭(sqlSeeion.close())的时候,才会转存到二级缓存中!二级缓存的作用域就是一个mapper.xml下的,不同的mapper.xml有自己不同的二级缓存。随着一级缓存的关闭,相同mapper.xml查询的对象就会转存到同一个二级缓存下,不同mapper.xml查询到的对象会放在不同的二级缓存下。不同的mapper.xml要么使用自己的二级缓存池,要么引用别的mapper.xml的二级缓存池。

【学习笔记】Mybatis框架学习及个人感悟相关推荐

  1. 开源鸿蒙南向嵌入学习笔记——NAPI框架学习(一)

    开源鸿蒙南向嵌入学习笔记--NAPI框架学习(一) 前言--系列介绍 本系列文章主要是记录笔者在鸿蒙南向的学习与工作中的知识点笔记记录,其中不止会针对鸿蒙中的学习问题进行思考与记录,也会对涉及到的一些 ...

  2. 学习笔记-TP5框架学习笔记\(路由\)

    TP5框架简单理解 (PS:只做粗略.关键知识的记录,TP程序的开始.详情请阅读官方手册) 1. 架构总览 TP程序的开始 PHP >=5.3.0, PHP7 ThinkPHP5.0应用基于MV ...

  3. 学习笔记-TP5框架学习笔记(杂项)

    3. 数据库 相信跟完那几个SQLi漏洞的代码分析,就已经对TP种数据库操作有些了解了,故略. 4. 模板 4.1 变量输出 // index.php 控制器 use think\Controller ...

  4. mybatis框架--学习笔记(下)

    上篇:mybatis框架--学习笔记(上):https://blog.csdn.net/a745233700/article/details/81034021 8.高级映射: (1)一对一查询: ①使 ...

  5. mybatis框架--学习笔记(上)

    使用JDBC操作数据库的问题总结: (1)数据库连接,使用时创建,不使用时立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能. 设想:使用数据库连接池管理数据库连接. (2) ...

  6. [Spring+SpringMVC+Mybatis]框架学习笔记(四):Spring实现AOP

    上一章:[Spring+SpringMVC+Mybatis]框架学习笔记(三):Spring实现JDBC 下一章:[Spring+SpringMVC+Mybatis]框架学习笔记(五):SpringA ...

  7. 2021年3月8日:MyBatis框架学习笔记02:利用MyBatis实现CRUD操作

    MyBatis框架学习笔记02:利用MyBatis实现CRUD操作 在第一节课中我们在UserMapper.xml里定义了两个查询语句:findById和findAll,对应的在UserMapper接 ...

  8. 拉勾教育Java训练营学习感受/学习笔记--MyBatis

    拉勾教育Java训练营学习感受/学习笔记–MyBatis 文章目录 拉勾教育Java训练营学习感受/学习笔记--MyBatis 1.普通jdbc操作流程以及问题 1.1 流程 1.2 问题 2.自定义 ...

  9. 免费学习机器学习和深度学习的源码、学习笔记和框架分享

    机器学习和深度学习的免费学习源码.学习笔记和框架分享 python笔记 源码 python导入模块的的几种方式 在python中,字典按值排序 python中set的基本常用方法 python取出fr ...

  10. spring struts2 mybatis框架学习总结(mvc三层架构)

    spring struts2 mybatis框架学习总结(mvc三层架构) ssi的框架主要是由struts2,spring以及ibatis组成,他们负责各层之间的交互与协作,从而实现整个web端的功 ...

最新文章

  1. static和global的区别
  2. windows server 2003 安装显卡驱动
  3. 专家:中国房地产泡沫崩溃时间就是今明二年
  4. python中 __name__及__main()__的妙处
  5. crmeb单商户java版安装_前端配置说明 · CRMEB 单商户Java版 帮助文档 · 看云
  6. Java中synchronized和Lock的区别
  7. 人类染色质开放状态数据库,ATACdb使用介绍
  8. Linux系统编程11:进程入门之详细阐述进程的一些状态区分僵尸状态和孤儿状态动图演示
  9. python 数列第几项开始,数值超过1200
  10. 移动端页面弹幕小Demo实例说明
  11. JSON七彩影视双端二开修复源码
  12. matlab函数grid,Matlab基本函数-grid、box函数
  13. 【lidar】单目深度估计与伪雷达点云、可视化
  14. 【已解决】微信内置浏览器清缓存
  15. 程序猿想平稳度过35岁中年危机?不妨试着考个研
  16. 互联网公司为何杀死中年人?三年醇,五年陈,十年滚!
  17. selenium使用小瓜子
  18. 【软考】系统集成项目管理工程师(十四)项目合同管理
  19. MAT(Memory Analyzer Tool)工具使用超详细版
  20. 中国安防服务运营探讨:视频监控运维服务发展趋势

热门文章

  1. HTML如何实现PDF在线预览
  2. 跨境电商APP开发功能分析
  3. 腾讯拿下NBA,巨头都花巨资买内容
  4. 真正智慧化的酒店是怎么样的?高仙携手凯悦、喜来登、威斯汀共同打造数字化清洁
  5. 视觉SLAM③:三维空间的刚体运动
  6. 会p图的程序猿---ps基础(人像修图)
  7. A股全市场分笔行情数据【最新】
  8. 太原理工 Web程序设计基础 学某通 (第二章 web前端开发概述)
  9. 销售记录代码html,使用4个不同类型的销售记录表汇总成当日销量表及月销量表...
  10. 怎样判断一个网站是不是模板建站