Demo 
导入相关依赖 
 
1 2 3 4 5 6 7 8 9 10 <dependency >     <groupId > org.mybatis</groupId >      <artifactId > mybatis</artifactId >      <version > 3.5.0</version >  </dependency > <dependency >     <groupId > mysql</groupId >      <artifactId > mysql-connector-java</artifactId >      <version > 8.0.19</version >  </dependency > 
 
 
编写MyBatis核心配置文件 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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 > 	     <properties  resource ="db.properties" />      <environments  default ="development" >          <environment  id ="development" >              <transactionManager  type ="JDBC" />              <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 >      <mappers >          <package  name ="com.atomsk.dao" />      </mappers >  </configuration > 
 
编写MyBatis工具类,便于使用 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  MybatisUtils   {    private  static  SqlSessionFactory factory;     static  {         try  {                          String resource = "mybatis-config.xml" ;             InputStream inputStream = Resources.getResourceAsStream(resource);             factory = new  SqlSessionFactoryBuilder().build(inputStream);         } catch  (IOException e) {             System.out.println("创建SqlSession工厂失败" );             e.printStackTrace();         }     }     public  static  SqlSession getSqlSession ()   {         return  factory.openSession();     } } 
 
创建实体类 
编写Mapper接口类 
 
1 2 3 4 @Mapper public  interface  UserMapper   {    List<User> getAllUser ()  ; } 
 
编写Mapper.xml配置文件 
 
1 2 3 4 5 6 7 8 <?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.atomsk.dao.UserMapper" >     <select  id ="getAllUser"  resultType ="com.atomsk.pojo.User" >          select * from user;     </select >  </mapper > 
 
Mavan静态资源过滤 
 
1 2 3 4 5 6 7 8 9 10 11 12 <build >     <resources >          <resource >              <directory > src/main/java</directory >              <includes >                  <include > **/*.properties</include >                  <include > **/*.xml</include >              </includes >              <filtering > false</filtering >          </resource >      </resources >  </build > 
 
编写测试类 
 
1 2 3 4 5 6 7 8 9 10 11 12 public  class  UserMapperTest   {    @Test      public  void  test ()   {         SqlSession sqlSession = MybatisUtils.getSqlSession();         UserMapper userMapper = sqlSession.getMapper(UserMapper.class ) ;         List<User> users = userMapper.getAllUser();         for  (User user : users) {             System.out.println(user);         }         sqlSession.close();     } } 
 
CRUD 
select
 
参数:
rersultType:SQL语句返回值类型。【完整的类名或者别名】 
parameterType:传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】 
 
示例:根据 用户名 和 密码 查询用户
思路一:直接在方法中传递参数
1、在接口方法的参数前加 @Param属性
2、Sql语句编写的时候,直接取@Param中设置的值即可,不需要单独设置参数类型
1 2 3 4 5 User selectUserByNP(@Param("username") String username,@Param("password") String pwd); <select  id ="selectUserByNP"  resultType ="com.kuang.pojo.User" >     select * from user where name = #{username} and pwd = #{password} </select > 
 
思路二:使用万能的Map
1、在接口方法中,参数直接传递Map;
1 User selectUserByNP2 (Map<String,Object> map)  ;
 
2、编写sql语句的时候,需要传递参数类型,参数类型为map
1 2 3 <select  id ="selectUserByNP2"  parameterType ="map"  resultType ="com.kuang.pojo.User" > 	select * from user where name = #{username} and pwd = #{password} </select > 
 
3、在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求!
1 2 3 4 Map<String, Object> map = new  HashMap<String, Object>(); map.put("username" ,"小明" ); map.put("password" ,"123456" ); User user = mapper.selectUserByNP2(map); 
 
总结:如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可
insert update delete  操作需要提交事务!
 
关于@Param
 
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。 
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。 
如果参数是 JavaBean , 则不能使用@Param。 
不使用@Param注解时,参数只能有一个,并且是Javabean。 
 
#与$的区别
 
配置解析 
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 
能配置的内容如下: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器) 
 
注意元素节点的顺序!顺序不对会报错  
可以阅读 mybatis-config.xml 上面的dtd的头文件!
environments元素
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <configuration >        <properties  resource ="db.properties" />     <environments  default ="development" >         <environment  id ="development" >             <transactionManager  type ="JDBC" />             <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 >     <mappers >         <mapper  resource ="mapper/UserMapper.xml" />     </mappers >  </configuration > 
 
mappers元素
 
mappers 
引入资源方式 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <mappers > 	<mapper  resource ="org/mybatis/builder/PostMapper.xml" />  </mappers > <mappers > 	<mapper  url ="file:///var/mappers/AuthorMapper.xml" />  </mappers > <mappers > 	<mapper  class ="org.mybatis.builder.AuthorMapper" />  </mappers > <mappers > 	<package  name ="org.mybatis.builder" />  </mappers > 
 
Mapper文件 
1 2 3 4 5 6 7 <?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.kuang.mapper.UserMapper" >     </mapper > 
 
namespace中文意思:命名空间,作用如下:
namespace的命名必须跟某个接口同名 
接口中的方法与映射文件中sql语句id应该一一对应 
 
 
 
typeAliases优化
 
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
1 2 3 4 <typeAliases >    <typeAlias  type ="com.kuang.pojo.User"  alias ="User" />  </typeAliases > 
 
当这样配置时,User可以用在任何使用com.kuang.pojo.User的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
1 2 3 <typeAliases >    <package  name ="com.kuang.pojo" />  </typeAliases > 
 
每一个在包 com.kuang.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
若有注解,则别名为其注解值。见下面的例子:
1 2 3 4 @Alias ("user" )public  class  User   {  ... } 
 
日志 如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题。
对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用Mybatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
SLF4J 
Apache Commons Logging 
Log4j 2 
Log4j 
JDK logging 
 
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。
标准日志实现 指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
1 2 3 <settings >        <setting  name ="logImpl"  value ="STDOUT_LOGGING" />  </settings > 
 
测试,可以看到控制台有大量的输出!我们可以通过这些输出来判断程序到底哪里出了Bug
Log4j 简介: 
通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件…. 
我们也可以控制每一条日志的输出格式; 
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。 
 
使用步骤: 
1、导入log4j的包
1 2 3 4 5 <dependency >    <groupId > log4j</groupId >     <artifactId > log4j</artifactId >     <version > 1.2.17</version >  </dependency > 
 
2、配置文件编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 log4j.rootLogger =DEBUG,console,file log4j.appender.console  = org.apache.log4j.ConsoleAppender log4j.appender.console.Target  = System.out log4j.appender.console.Threshold =DEBUG log4j.appender.console.layout  = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern =[%c]-%m%n log4j.appender.file  = org.apache.log4j.RollingFileAppender log4j.appender.file.File =./log/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.sql =DEBUG log4j.logger.java.sql.Statement =DEBUG log4j.logger.java.sql.ResultSet =DEBUG log4j.logger.java.sql.PreparedStatement =DEBUG 
 
3、setting设置日志实现
1 2 3 <settings >    <setting  name ="logImpl"  value ="LOG4J" />  </settings > 
 
4、在程序中使用Log4j进行输出!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static  Logger logger = Logger.getLogger(MyTest.class ) ;@Test public  void  selectUser ()   {   logger.info("info:进入selectUser方法" );    logger.debug("debug:进入selectUser方法" );    logger.error("error: 进入selectUser方法" );    SqlSession session = MybatisUtils.getSession();    UserMapper mapper = session.getMapper(UserMapper.class ) ;    List<User> users = mapper.selectUser();    for  (User user: users){        System.out.println(user);   }    session.close(); } 
 
5、测试,看控制台输出!
使用Log4j 输出日志 
可以看到还生成了一个日志的文件 【需要修改file的日志级别】 
 
分页 
limit实现分页
 
思考:为什么需要分页? 
在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
使用Limit实现分页 
1 2 3 4 5 6 7 8 9 10 11 #语法 SELECT * FROM table LIMIT stratIndex,pageSize SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15   #为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:    SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.   #如果只给定一个参数,它表示返回最大的记录行数目:    SELECT * FROM table LIMIT 5; //检索前 5 个记录行   #换句话说,LIMIT n 等价于 LIMIT 0,n。 
 
步骤: 
1、Mapper接口,参数为map
1 2 List<User> selectUser (Map<String,Integer> map)  ;
 
2、修改Mapper文件
1 2 3 <select  id ="selectUser"  parameterType ="map"  resultType ="user" >   select * from user limit #{startIndex},#{pageSize} </select > 
 
3、在测试类中传入参数测试
推断:起始位置 =  (当前页面 - 1 ) * 页面大小 
 
 
   int currentPage = 1;  //第几页    int pageSize = 2;  //每页显示几个    Map<String,Integer> map = new HashMap<String,Integer>();    map.put(“startIndex”,(currentPage-1)*pageSize);    map.put(“pageSize”,pageSize);
   List users = mapper.selectUser(map); ``` 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 PageHelper 官方文档:https://pagehelper.github.io/ ## 多对一的处理 多对一的理解: - 多个学生对应一个老师 - 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师! > 数据库设计 ```mysql CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) 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 
 
按结果嵌套处理
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select  id ="getStudents2"  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" >    <id  property ="id"  column ="sid" />     <result  property ="name"  column ="sname" />         <association  property ="teacher"  javaType ="Teacher" >         <result  property ="name"  column ="tname" />     </association >  </resultMap > 
 
按查询嵌套处理
 
1 2 3 4 5 6 7 8 9 10 <select  id ="getStudents"  resultMap ="StudentTeacher" >     select * from student </select > <resultMap  id ="StudentTeacher"  type ="Student" >          <association  property ="teacher"   column ="tid"  javaType ="Teacher"  select ="getTeacher" />  </resultMap > <select  id ="getTeacher"  resultType ="teacher" >     select * from teacher where id = #{id} </select > 
 
一对多的处理 一对多的理解:
一个老师拥有多个学生 
如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)! 
 
按结果嵌套处理
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <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=#{id} </select > <resultMap  id ="TeacherStudent"  type ="Teacher" >     <result   property ="name"  column ="tname" />      <collection  property ="students"  ofType ="Student" >          <result  property ="id"  column ="sid"  />          <result  property ="name"  column ="sname"  />          <result  property ="tid"  column ="tid"  />      </collection >  </resultMap > 
 
按查询嵌套处理
 
1 2 3 4 5 6 7 8 9 10 11 12 <select  id ="getTeacher2"  resultMap ="TeacherStudent2" > 	select * from teacher where id = #{id} </select > <resultMap  id ="TeacherStudent2"  type ="Teacher" >        <collection  property ="students"  javaType ="ArrayList"  ofType ="Student"  column ="id"  select ="getStudentByTeacherId" />  </resultMap > <select  id ="getStudentByTeacherId"  resultType ="Student" > 	select * from student where tid = #{id} </select > 
 
小结
 
按照查询进行嵌套处理就像SQL中的子查询
 
按照结果进行嵌套处理就像SQL中的联表查询
 
association是用于一对一和多对一,而collection是用于一对多和多对多的关系
 
JavaType 和ofType 都是用来指定对象类型的
    JavaType是用来指定pojo中属性的类型
    ofType指定的是映射到list集合属性中pojo的类型。
 
 
延迟加载 在对应的四种表关系中
一对多,多对多:通常情况下我们都是采用延迟加载。 
多对一,一对一:通常情况下我们都是采用立即加载。 
 
开启延迟加载
1 2 3 <settings >     <setting  name ="lazyLoadingEnabled"  value ="true" />   <setting  name ="aggressiveLazyLoading"  value ="false" />  </settings > 
 
动态SQL 
介绍
 
什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句. 
if 和Where语句
 
修改上面的SQL语句;
1 2 3 4 5 6 7 8 9 10 11 <select  id ="queryBlogIf"  parameterType ="map"  resultType ="blog" >   select * from blog    <where >         <if  test ="title != null" >            title = #{title}        </if >         <if  test ="author != null" >            and author = #{author}        </if >     </where >  </select > 
 
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
Set
 
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 <update  id ="updateBlog"  parameterType ="map" >   update blog      <set >           <if  test ="title != null" >              title = #{title},          </if >           <if  test ="author != null" >              author = #{author}          </if >       </set >    where id = #{id}; </update > 
 
choose语句
 
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select  id ="queryBlogChoose"  parameterType ="map"  resultType ="blog" >   select * from 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片段
 
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
提取SQL片段: 
1 2 3 4 5 6 7 8 <sql  id ="if-title-author" >    <if  test ="title != null" >        title = #{title}    </if >     <if  test ="author != null" >        and author = #{author}    </if >  </sql > 
 
引用SQL片段: 
1 2 3 4 5 6 7 8 <select  id ="queryBlogIf"  parameterType ="map"  resultType ="blog" >   select * from blog    <where >                 <include  refid ="if-title-author" > </include >             </where >  </select > 
 
注意:
①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
Foreach
 
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
2、编写SQL语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select  id ="queryBlogForeach"  parameterType ="map"  resultType ="blog" >   select * from blog    <where >                 <foreach  collection ="ids"   item ="id"  open ="and ("  close =")"  separator ="or" >            id=#{id}        </foreach >     </where >  </select > 
 
小结 :其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。
缓存 
Mybatis缓存
 
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
 
MyBatis系统中默认定义了两级缓存:一级缓存 和二级缓存 
 
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
 
 
一级缓存 一级缓存也叫本地缓存:
与数据库同一次会话期间查询到的数据会放在本地缓存中。 
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库; 
 
一级缓存失效的四种情况
 
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
当前sqlSession的缓存中不存在这个数据 
sqlSession相同,调用了增删改,commit(),close()等方法时,就会清空一级缓存 
sqlSession相同,手动清除了一级缓存,sqlSession.clearCache() 
 
二级缓存 
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
 
由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
 
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
 
使用二级缓存时,所缓存的类一定要序列化
 
 
使用步骤
 
1、让mybatis框架支持二级缓存【mybatis-config.xml】
1 <setting  name ="cacheEnabled"  value ="true" /> 
 
2、让当前mapper映射文件支持二级缓存【xxxMapper.xml】
1 2 3 4 5 6 7 8 9 10 1. 	<mapper  namespace ="" >      	<cache />  	</mapper >  或者 2. <cache    eviction ="FIFO"   flushInterval ="60000"   size ="512"   readOnly ="true" /> 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 
 
3、让当前的操作支持二级缓存(在select标签中配置 userCache="true")
1 2 3 <select  id ="queryUserById"  resultType ="user"  useCache ="true" > 	select * from user where id = #{uid} </select > 
 
4、代码测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public  void  testQueryUserById ()  {   SqlSession session = MybatisUtils.getSession();    SqlSession session2 = MybatisUtils.getSession();    UserMapper mapper = session.getMapper(UserMapper.class ) ;    UserMapper mapper2 = session2.getMapper(UserMapper.class ) ;    User user = mapper.queryUserById(1 );    System.out.println(user);    session.close();    User user2 = mapper2.queryUserById(1 );    System.out.println(user2);    System.out.println(user==user2);    session2.close(); } 
 
结论
 
只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据 
查出的数据都会被默认先放在一级缓存中 
只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中 
 
缓存原理图
 
EhCache
 
第三方缓存实现–EhCache: