假装是小白之重学MyBatis(一)

在工作中发现对MyBatis还是有理解不到位的地方,所以就打算重新学习一下MyBatis。

简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

首先是MyBatis给自己的定位是持久层框架,那什么是持久层,简单的说就是和数据库沟通的那一层,也就是MVC模式下的dao(Data Access Object)层,dao层的代码负责将业务代码变成对数据库表的操作,至于为什么叫持久层,我觉得是跟数据库的持久性特点有关系呢!

在dao层没有任何框架之前,我们是直接使用原生的jdbc来操纵数据库的数据,拼接SQL,设置参数,获取结果集重复性的工作常常让人十分厌烦,但是JDBC从设计思路上讲也足够优秀了,做到了跨数据库,让使用者不必关心是哪个数据库,而采取不同的操作,JDBC就是一组接口,由各大数据库厂商提供对应的实现类,所以也不大可能做完全的定制的化操作,所以JDBC的设计思想就是宽泛一点。
假装是小白之重学MyBatis(一)_第1张图片
但是我们希望简单点,所以如果你看视频去学习的话,基本上学完JDBC,就会讲如何封装一个工具类JdbcUtils,来避免重复代码的编写,但这并不是一个Java程序员的痛点,对吗?Java社区也关注到了这个问题,开始着手对JDBC进行扩展,进行升级。这也就是MyBatis、Hibernate、Spring Data JPA等ORM框架。
假装是小白之重学MyBatis(一)_第2张图片

等等你刚才又提到了一个名词,ORM框架,那什么是ORM框架? ORM Object Relational Mapping 即对象关系映射,听起来好抽象啊! 不要着急,听我细细道来,Java是一门面向对象的语言,我们现在普遍使用的数据库是关系型数据库(表为主要形式),ORM的思想就是能否将表映射为对象呢? 一条数据记录就是一个对象。

所以MyBatis是一款优秀的持久层、ORM框架,在JDBC的基础上进行扩展、封装,免除了几乎所有JDBC代码以及设置参数和获取结果集的工作,大大简化了持久层代码开发。

对简化了持久层代码的开发,简单点,简单点,我们都喜欢简单的东西。

如何学习一门技术?

一般情况,学一门框架,最好还是去官网去学,以前我是图速度快,去B站找的视频。现在发现MyBatis官方写的教程挺不错的,更让我喜欢的是有中文版本:
假装是小白之重学MyBatis(一)_第3张图片
好到我让我觉得我这篇博客,是不是还是有必要写。但是思虑再三,还是打算写,官方文档配合自己的理解,让自己的知识更成系统。

准备工作

要用MyBatis,我们首先要引入MyBatis,MyBatis是在原生JDBC的基础上做扩展,所以我们要引入对应的数据库驱动(数据库驱动就是数据库厂商实现的JDBC),本篇我们使用的是MySQL,Druid来管理数据库连接。 本篇我们依然使用Maven来搭建项目:
对应的依赖如下:


            org.mybatis
            mybatis
            3.5.6
        
        
            mysql
            mysql-connector-java
            5.1.47
        
        
            com.alibaba
            druid
            1.2.5
        
        
            org.slf4j
            slf4j-api
            1.7.30
        
        
            org.slf4j
            slf4j-log4j12
            1.7.30
            test
        

slf4j是日志框架,输出的信息会更细致,建议引入。

如果你不会用maven

建议你去学maven,参看我这篇博客: Maven学习笔记,非常通俗易懂的入门。

如果你不想学Maven,想用jar包模式,也行,我的博客就是这么贴心,哈哈哈哈。

  1. 首先进入MyBatis官网

假装是小白之重学MyBatis(一)_第4张图片

  1. 假装是小白之重学MyBatis(一)_第5张图片

3.假装是小白之重学MyBatis(一)_第6张图片

  1. 假装是小白之重学MyBatis(一)_第7张图片

第一个MyBatis 程序

首先我们要建一个配置文件

假装是小白之重学MyBatis(一)_第8张图片
假装是小白之重学MyBatis(一)_第9张图片

对了还要建一个表,如果你不懂的建表,本篇文章可能就不适合你,我建的表叫Blog。
表语句我就不放了,最近重学SSM,写了一些例子,都放在GitHub上了,下面是链接:

大概解释一下配置文件




    
    
    
    
        
            
            
            
            
                
                
                
                
            
        
      
    
        
        
    

Hello World

新建一个接口

public interface BlogMapper {
    Blog selectBlog(Long id);
}

新建一个xml





      
    

测试代码

 public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        // 读取配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 开启一个会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 加载指定的接口
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        // 调用接口中的方法,相当于执行对应mapper中的查询语句
        Blog blog = blogMapper.selectBlog(1L);
        // 打印查询语句
        System.out.println(blog);
    }

执行结果:

总结一下

第一个程序可能还比较晕哈,先跟着做,然后我们来解释一下MyBatis做了什么:
假装是小白之重学MyBatis(一)_第10张图片
那BlogMaper.java究竟是怎么和BlogMapper.xml关联起来的呢? 还记得上面的配置吗?
假装是小白之重学MyBatis(一)_第11张图片
假装是小白之重学MyBatis(一)_第12张图片
本质上还是动态代理,运行时创建接口的实现类,如果你不懂什么是动态代理,请参看:

用MyBatis操纵数据库的基本流程:

  • 新建接口
  • 建xml,这里要注意一下,XML也不能瞎建,上面的约束还是要注意一下:

假装是小白之重学MyBatis(一)_第13张图片
不用记也没关系,可以在MyBatis官网拷贝一下:
假装是小白之重学MyBatis(一)_第14张图片
假装是小白之重学MyBatis(一)_第15张图片

  • 在配置文件的mapper标签,开启扫描标签,关联接口和xml文件(可以批量设定的,后面会讲)
  • 然后SqlSession对象的获取指定接口的方法,即可调用对应的SQL语句。

仔细体会下,这样相对于原生的JDBC是不是更加清晰了呢。

传递参数

上面我们在调用BlogMapper中的selectBlog仅仅只是在接口中写了参数,MYBatis就能自动的将#{id}替换为我们传递的参数,是不是很强大呢! 这种方式也支持多个参数,但是该SQL需要的参数有七八个怎么办?接口方法上写七八个参数? 其实也行,那传的是一个数组或者List呢? MyBatis能遍历一下吗? MyBatis: 当然支持。
对于数据库来说,查总是最让我们关心的,select标签的属性也是最多的,如下面代码所示:

这些属性在MyBatis官网有详细的介绍:
假装是小白之重学MyBatis(一)_第16张图片
假装是小白之重学MyBatis(一)_第17张图片
我们这里只拎出来常用的来介绍,在传递参数这里,我们介绍的就是parameterType(参数类型)属性,这个属性是可选的,MyBatis可以通过Typehandler来推断出来传递的参数,来将我们SQL语句中的#{id}(我们下文会统一称之为占位符)替换掉。
注意单个参数,占位符中参数名和方法中的参数名无需保持一致。多个参数时,就需要启用@Param注解了。@Param中的属性值要和占位符中的参数名保持一致,不然MyBatis拿到两个参数值,无法推断出应该用哪个值替换占位符。
假装是小白之重学MyBatis(一)_第18张图片

如果你想要用对象

直接传对象(Map也是一样), 然后在占位符中写对应的属性名(Map的时候是key)即可:


    

测试代码:

public static void main(String[] args) throws IOException {
        selectByObj();
    }
    private static void selectByObj() throws IOException {
        BlogMapper blogMapper = getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setId(1L);
        blog.setName("aa");
        blog = blogMapper.selectBlogByObj(blog);
        System.out.println(blog);
    }
    public static  T getMapper(Class t) throws IOException {
        String resource = "mybatis-config.xml";
        // 读取配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 开启一个会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return (T) sqlSession.getMapper(t);
    }

运行结果:

下面再介绍使用的时候,不会再贴getMapper和main方法,只写对应的方法。

集合

遍历List 、数组 、Map

集合常常被用来构建in语句,那么在xml中怎么遍历呢? 通过foreach标签来遍历,像下面这样:

   

这个foreach标签就是把java中foreach映射进到xml了而已。item是迭代元素 , list是迭代的集合。open、close用于指定拼接形成的字符串,以及集合项迭代的分隔符。也就是说假设我传入的List有1,2这两个元素,最终形成的sql语句就会是下面这样:

 select * from Blog where id in (1,2)

看MyBatis 多么的智能。假如集合类型是Map的时候,index 是键,item 是值。
上面我查到的可能是多个对象,那在MyBatis中应该怎么接收呢?只用将该标签id对应的方法改为List就行。

  List selectBlogByArray(Long[] idLongArray);

默认情况下,collection即为集合名的纯小写,比如传入的是List类型,那么Collection中就应该写list。如果是数组就应该写Array。假如用@Param指定了参数名,那么就写@Param中指定的参数名。

返回类型

Map

上面我们已经讲了返回一条数据和返回多条数据,这个用Map接收返回类型,看上去有点违背直觉,事实上他也是完全符合直觉的。我们用Map接收一下试试看:

接口中声明的方法:

   Map selectAllReturnMap();
 private static void selectAllReturnMap() throws IOException {
        BlogMapper blogMapper = getMapper(BlogMapper.class);
        System.out.println(blogMapper.selectAllReturnMap());
    }

测试结果如下:
假装是小白之重学MyBatis(一)_第19张图片
大意就是返回了三个,你用一个接收,接收不了。
可能这个时候又同学就会问了,Map不是能装多组值吗? 为啥收不了三条记录,那我们想一下Map的key是不是不能重复,三条记录的key都是属性名,某种意义上一个Map就是一个对象,key就是属性名,value就是属性值。所以我们应该这么收:

List> selectAllReturnMap();

测试结果:
假装是小白之重学MyBatis(一)_第20张图片

{占位符} VS ${占位符}

上面我们用的替换参数的占位符,都是#号开头的,MyBatis会把#{参数名}替换为我们调用接口对应方法时传递的参数值,一般我们称之为#号占位符,其实还有一种是以$开头的。我们称之为dollar(刀乐)占位符。默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。
下面我们用一个例子来感受一下两者的不一样,我们先配下日志,日志输出真正执行的SQL,方便我们分析问题
假装是小白之重学MyBatis(一)_第21张图片
我们按照要求引入对应的依赖:

 
            org.apache.logging.log4j
            log4j-core
            2.12.1
        
        
            org.apache.logging.log4j
            log4j
            2.14.0
            pom
        
        
            commons-logging
            commons-logging
            1.2
        

然后在resources建一个文件log4j.properties,文件内容如下:

# 全局日志配置
log4j.rootLogger=DEBUG,ERROR, stdout
# MyBatis 日志配置 会输出执行包下的详细信息
log4j.logger.org.mybatis.example=DEBUG 
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

这个照着贴就行,介绍日志并不是本篇的内容,我们是借助日志来研究井号占位符和美元占位符的不同之处。
然后在配置文件中开启日志:

   
        
    

假装是小白之重学MyBatis(一)_第22张图片

号占位符

xml中的代码:

  

BlogMapper.中的代码:

List selectByMark(@Param("id") String id,@Param("name") String name);

测试代码:

  private static void markVsDollar() throws IOException {
        BlogMapper blogMapper = getMapper(BlogMapper.class);
        blogMapper.selectByMark("1","aa; delete Blog;");
    // blogMapper.selectByDollar("1","aa; delete Blog");
    }

执行结果:

DEBUG [main] - Created connection 551479935.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@20deea7f]
DEBUG [main] - ==>  Preparing: select * from Blog where id = ? and name = ?
DEBUG [main] - ==> Parameters: 1(String), aa or 1 = 1(String)
DEBUG [main] - <==      Total: 0

所以最终的SQL语句就是: select * from Blog where id = '1' and name = 'aa or 1 = 1'
我们的数据库并没有这样的数据,所以一条这样的数据都没查出来。

$号占位符

xml中的代码:

  
List selectByDollar(@Param("id") String id, @Param("name") String name);

测试代码:

  private static void markVsDollar() throws IOException {
        BlogMapper blogMapper = getMapper(BlogMapper.class);
        blogMapper.selectByDollar("1","1 or  1 = 1;");
    }

执行结果:

DEBUG [main] - Created connection 1327006586.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f18837a]
DEBUG [main] - ==>  Preparing: select * from Blog where id = 1 and name = 1 or 1 = 1;
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 2

各位仔细的对比一下两个实际执行的SQL语句,一个Parameters有值,有参数类型,一个没有,直接是原样替换。
第二种事实上被称之为SQL注入,查到了不应该查到的记录。但是$号占位符也不是一无是处,比如在排序的时候,根据前端传递的字段来进行排序:

ORDER BY ${columnName}

可能有同学会说,这样是不是似乎也有注入风险,不按约定的字段,随便穿了一个过来,然后不就报错了,避免这个问题可以在Java中做判断,只有是约定的字段才会传给真正执行的SQL。

总结一下

井号占位符会自动给我们转义,根据类型来判断转不转义,如果是字符串类型,MyBatis会自动为我们将参数上加上引号。如果是数字类型,就不会加上。
美元占位符是原样替换,有SQL注入的风险,但有的时候在我们并不想MyBatis为我们拼上引号的时候,比如说根据前端传递的字段来进行排序,所以$占位符要慎用。

update、delete、insert、调用存储过程

数据变更语句 insert,update 和 delete 的实现非常接近:





parameterType类型和select标签的parameterType使用方式一样,我们主要讲statementType、useGeneratedKeys。
statementType: 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
JDBC基础不扎实的同学,可能会问Statement、PreparedStatement 、CallableStatement是啥?
简单的说,Statement接口提供了执行语句和获取结果的基本方法;PreparedStatement接口添加了处理输入参数的方法;
CallableStatement接口添加了调用存储过程核函数以及处理输出参数的方法。

useGeneratedKeys 使用示例



     insert into Blog (name)
     values (#{name})

java代码:

   private static void insertEntity() throws IOException {
        String resource = "mybatis-config.xml";
        // 读取配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 开启一个会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setName("aaa");
        blogMapper.insertEntity(blog);
        // 记得提交
        sqlSession.commit();
        // 关闭会话,不然新增不会成功
        sqlSession.close();
        System.out.println(blog);
    }

所以上面的getMapper方法,我们还要再改造一下,实际使用中也就是SSM(Spring Spring MVC MyBatis)整合之后,引入数据库连接池之后,连接使用完毕之后会还给连接池,这里我们就不改造了。
测试结果:
假装是小白之重学MyBatis(一)_第23张图片
数据库结果:
假装是小白之重学MyBatis(一)_第24张图片
数据库支持自增主键可以这么搞,不支持的就不可以这么搞了。

常用标签

上面我们在介绍参数类型是集合时,已经介绍了遍历标签, , 但是有循环,怎么可以没有判断,switch呢。那怎么把逻辑判断移入xml中呢? 将判断变成一个一个标签吗? 那得记住多少标签啊? MyBatis的前身iBatis就这么做的,MyBatis做出的改变就是通过OGNL表达式配合if标签来完成逻辑判断。

OGNL表达式简介

OGNL(Object-Graph Navigation Language)是一种表达式语言(EL),简单来说就是一种简化了的Java属性的取值语言,在传统的Struts框架,以及MyBatis中都有大量使用,开源Java诊断利器Arthas也使用它做表达式过滤,简单但不失灵活的设计衍生出来了好多高阶玩法。

我们这里简单的介绍一下常用的,详情可以参看OGNL官方文档: http://commons.apache.org/pro...

OGNL生来就是为了简化Java属性的取值,比如想根据名称name引用当前上下文环境中的对象,则直接键入即可,如果想要引用当前上下文环境中对象text的属性title,则键入text.title即可。如果调用对象的方法,直接通过对象.方法名()即可。
访问数组,直接通过数组名[索引]访问即可。

判断参数不为null 和 集合size大于0

 

参数值等于某个字符串

   

choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。为了丰富我们的适用场景,我们又向Blog表里加了两个字段:title和status,以前只有id,name。
示例:

   

如果title不为空,就搜索title,如果name不为空, 就模糊搜索name。如果这两个都是空,就去搜索status 不是active的。

where、trim、set

前面的示例我们已经解决了拼接SQL的问题,下面我们来看下面一个SQL:

  select * from Blog where id in
        
            
                #{item}
            
        

如果条件不成立,SQL就会变成这样:

select * from Blog where id in

很明显这会报错,那为了避免这种情况,我们是不是要在where 后面拼接一个 1 = 1呢? 然后SQL就变成了下面这样:

  select * from Blog where  1 = 1
        
         id in
            
                #{item}
            
        

但是这相当不优雅,MyBatis提供了标签,where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。所以我们的SQL可以就变成了这样:

   

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:


  ...

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:


  update Author
    
      username=#{username},
      password=#{password},
      email=#{email},
      bio=#{bio}
    
  where id=#{id}

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:


  ...

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

bind标签

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文:

别名

还记得我们在配置文件中配置的mappers吗? 我们其实也可以批量配置 ,批量关联:


        
        
 

我们的resultType写的是全类名,那不能不写那么多呢? 可以,在配置文件中这样配置就可以了:

  
        
    

假装是小白之重学MyBatis(一)_第25张图片
注意这个顺序,只能按properties、settings、typeAliases这个顺序来。
然后我们写resultType中的值写类名就可以了。

类型转换器

现在我们来关注一下,MyBatis实现ORM的一组核心类,类型转换器。我们上面讲MyBatis将表的记录变成Java中的对象,那数据类型是怎么对的上的呢? 就是通过类型转换器:
假装是小白之重学MyBatis(一)_第26张图片
可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型, 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。
直接实现TypeHandler接口还是有点麻烦的,所以我们这里介绍的是继承BaseTypeHandler来体会MyBatis中类型转换器的强大功能。BaseTypeHandler概览:
假装是小白之重学MyBatis(一)_第27张图片
转换器是双向的,所以就有从数据库到Java的,从Java到数据库的:
setNonNullParameter Java到数据库就是剩下的剩下的三个get方法,从名字上我们就可以推断出来,是从数据库到Java的。
这次我们定义的类型转换器就是将Java的Boolean转成数据库中的int类型。

public class MyTypeHandler extends BaseTypeHandler {
    // java-DB
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException {
        if (parameter) {
            ps.setInt(i, 1);
        } else {
            ps.setInt(i, 0);
        }
    }

    // DB-java
    @Override
    public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getInt(columnName) == 1 ? true : false;
    }

    //  DB-java
    @Override
    public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getInt(columnIndex) == 1 ? true : false;
    }

    //  java-Db
    @Override
    public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getInt(columnIndex) == 1 ? true : false;
    }
}

然后在Blog实体和表中加入对应的字段,Blog为Boolean,Blog表为int ,这里不再做展示了。
然后我们在配置文件中注册使用这个类型转换器:


  

测试代码:

private static void selectBlogByCollection() throws IOException {
    BlogMapper blogMapper = getMapper(BlogMapper.class);
    List idLongList = new ArrayList<>();
    idLongList.add(1L);
    System.out.println(blogMapper.selectBlogByList(idLongList));
}

数据库中的值:
假装是小白之重学MyBatis(一)_第28张图片
测试结果:

转换成功。

总结一下

本篇主要讲的是MyBatis的基本使用,如何配置、传参、返回类型、占位符、常用标签、OGNL表达式。开篇讲的基本上都是使用频率很高的,这算是把MyBatis又重学了一遍,以前学MyBatis看视频,现在发现如果官方文档写的比较丰富的话,看官方文档是更好的选择。希望对会大家有所帮助

参考资料

你可能感兴趣的:(mybatis)