话不多说进入主题
为什么要使用Mybatis
在Mybatis出现以前,乃至于与mybatis相似的框架出现以前,我们是怎么连接数据库的? 依靠JDBC 而传统的JDBC连接数据库,大致是以下代码:
如果项目当中的业务比较复杂,表非常多,各种操作数据库的增删改查的方法也比较多的话,那么这样代码会重复出现很多次。 在每一段这样的代码里面,都需要自己去管理数据库的连接资源,如果忘记写close()了,就可能会造成数据库服务连接耗尽。 对于结果集的处理,要把 ResultSet转换成POJO的时候,必须根据字段属性的类型一个个地去处理。 还有一个问题就是处理业务逻辑和处理数据的代码是耦合在一起的。如果业务流程复杂,跟数据库的交互次数多,耦合在代码里面的SQL语句就会非常多。 总结一下:
- 重复代码
- 资源管理
- 结果集处理
- SQL耦合
那么身为程序员,我们自然而然就想写一个工具类来帮助我们做这些繁琐又枯燥无味的工作,把重复的代码和资源管子的代码封装进去,只传sql就行了。
Apache 的DbUtils
Apache在2003年的时候发布了一个叫Commons DbUtils的工具类,可以简化对数据库的操作。 官网链接.
DbUtils提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装。先创建一个QueryRunner对象。
代码语言:javascript复制private static QueryRunner queryRunner;
在QueryRunner的构造函数里面,可以传入一个数据源,比如这里用Hikari,这样我们就不需要再去写各种创建和释放连接的代码了。
代码语言:javascript复制queryRunner = new QueryRunner(dataSource);
我们通过这个工具类,提供获取QueryRunner 实例的方法。
方法封装解决了重复代码的问题,传入数据源解决了资源管理的问题。
怎么把结果集转换成对象呢?比如转换成POJO或者List或者Map?肯定不能一个个属性去set或者put 了。
我希望做到的是,只要指定一个类型,它就可以自动把结果集给我转换成这种类型。这个怎么实现呢?
为了避免给每种类型创建一个自动转换类,在DbUtils里面提供了一系列的支持泛型的ResultSetHandler,比如用来把结果集转换成JavaBean的,转换成List的,转换成Map的,等等。
只要在DAO层调用QueryRunner封装好的查询方法,传入一个指定了类型的Handler,它就可以自动把结果集转换成实体类Bean 或者List或者Map,不过这种自动映射,要求数据库的字段跟对象的属性名称完全一致,才可以实现自动映射。
Spring 的DbUtils
除了DbUtils之外,Spring 也对原生的JDBC进行了封装。 前面说到的几个问题是怎么解决的呢?
- 代码重复——Spring提供了一个模板方法JdbcTemplate,里面封装了各种各样的execute、query和update方法。 JDBCTemplate这个类(类的注释)∶ 它是JDCB的核心包的中心类。简化了JDBC的使用,可以避免常见的异常。它封装了JDBC的核心流程,应用只要提供SQL,提取结果集就可以了。它是线程安全的。 初始化的时候可以设置数据源,所以资源管理的问题也可以解决。
- 对于结果集的处理,Spring JDBC提供了一个RowMapper接口,可以把结果集转换成Java对象,它作为JdbcTemplate的参数使用。 比如要把查询tbl_emp表得到的结果集转换成Employee对象,就可以针对一个Employee 创建一个RowMapper对象,实现RowMapper接口,并且重写mapRow()方法。在mapRow()方法里面完成对结果集的处理。
通过这种方式,无论在多少个地方查询tbl_emp表,都不需要重复处理ResultSet,只要在每一个需要映射的地方传入这个RowMapper就可以了,减少了很多的重复代码。 当然,如果项目的表数量非常多的时候,每张表转换为POJO都要定义一个RowMapper,会导致类文件数量膨胀。 所以有没有办法让表里面一行数据的字段,跟POJO的属性自动对应起来,实现自动映射呢?我们肯定要解决两个问题, 一个就是名称对应的问题,从下划线到驼峰命名; 第二个是类型对应的问题,数据库的JDBC类型(例如char)和Java对象的类型(例如String)要匹配起来。
我们可以自己写一个支持泛型的 BaseRowMapper实现RowMapper接口,通过反射的方式自动获取所有属性,把表字段全部赋值到属性。这样,我们在使用的时候只要传入我们需要转换的类型就可以了,不用再单独创建一个个的RowMapper。
DbUtils和Spring JDBC,这两个对JDBC做了轻量级封装的框架,或者说工具类里面,帮助我们解决的问题:
- 对操作数据的增删改查的方法进行了封装;
- 无论是QueryRunner还是JdbcTemplate,都可以传入一个数据源进行初始 化,也就是资源管理这一部分的事情,可以交给专门的数据源组件去做,不用我们手动创建和关闭;
- 可以帮助我们映射结果集,无论是映射成List、Map还是POJO。
这两个工具已经可以帮助我们解决很大的问题了,但是还是存在一些不足:
- SQL语句都是写死在代码里面的,依旧存在硬编码的问题;
- 参数只能按固定位置的顺序传入(数组),它是通过占位符去替换的不能传入 对象和Map,不能自动映射;
- 在方法里面,可以把结果集映射成实体类,但是不能直接把实体类映射成数据 库的记录(没有自动生成SQL的功能);
- 查询没有缓存的功能,性能还不够好。
要解决这些问题,使用这些工具类还是不够的,这个时候用到ORM框架了。
什么是ORM
ORM的全拼是Object Relational Mapping,也就是对象与关系的映射,对象是程序里面的对象,关系是它与数据库里面的数据的关系。也就是说,ORM框架帮助我们解决的问题是程序对象和关系型数据库的相互映射的问题。 O:对象———M:映射———R:关系型数据库
比如 Hibernate 它是一个曾经很流行的ORM框架,2001年的时候就出了第一个版本。在使用Hibernate的时候,我们需要为实体类建立一些hbm的xml映射文件。 然后通过Hibernate提供(session)的增删改查的方法来操作对象。 我们操作对象就跟操作数据库的数据一样。Hibernate的框架会自动帮我们生成SQL语句(可以屏蔽数据库的差异),自动进行映射。这样我们的代码变得简洁了,程序的可读性也提高了。 当然映射配置文件也可以使用注解代替。
Hibernate的特性:
- 根据数据库方言自动生成SQL,移植性好;
- 自动管理连接资源(支持数据源);
- 实现了对象和关系型数据库的完全映射,操作对象就像操作数据库记录一样;
- 提供了缓存功能机制。
但是Hibernate在业务复杂的项目中使用也存在一些问题: 1、比如使用get()、update()、save()对象的这种方式,实际操作的是所有字段,没有办法指定部分字段,换句话说就是不够灵活。 2、自动生成SQL的方式,如果要基于SQL去做一些优化的话,是非常困难的,也就是说可能会出现性能的问题。 3、不支持动态SQL,比如分表中的表名、条件、参数变化等,无法根据条件自动生成SQL。
于是,我们需要一个更加灵活的框架。
mybatis
mybatis应运而生,它是一个半自动化的ORM框架。 “半自动化”的ORM框架 MyBatis 就解决了这几个问题。“半自动化”是相对于Hibernate的全自动化来说的。它的封装程度没有Hibernate那么高,不会自动生成全部的SQL语句,主要解决的是SQL和对象的映射问题。
MyBatis的前身是ibatis,2001年开始开发,是“internet”和“abatis (障碍物)”两个单词的组合。04年捐赠给Apache。2010年更名为MyBatis。 在MyBatis里面,SQL和代码是分离的,所以会写SQL基本上就会用MyBatis,没有额外的学习成本。
既然 MyBatis的目的是简化JDBC的操作,那么它必须要提供一个可以执行增删改查的对象,这个对象就是SqlSession接口,我们把它理解为跟数据库的一个连接,或者一次会话。 sqlSession怎么创建呢?因为数据源、MyBatis核心行为的控制(例如是否开启缓存)都在全局配置文件中,所以必须基于全局配置文件创建。这里它不是直接new出来的,而是通过一个工厂类创建的。 所以整个的流程就是这样的(如下代码)。最后我们通过SqlSession接口上的方法,传入我们的Statement lD来执行Mapper映射器中的SQL。 这样的调用方式,解决了重复代码、资源管理、SQL耦合、结果集映射这4大问题。 不过,这样的调用方式还是会存在一些问题: (1) Statement lID是硬编码,维护起来很不方便; (2) 不能在编译时进行类型检查,如果namespace或者Statement ID输错了,只能在运行的时候报错。
所以我们通常会使用第二种方式,也是新版的 MyBatis里面推荐的方式:定义一个Mapper接口的方式。这个接口全路径必须跟 Mapper.xml里面的namespace对应起来,方法也要跟Statement lD ——对应。通过执行接口方法,来执行映射器中的SQL语句。
总结一下,MyBatis的核心特性,或者说它可以解决哪些主要问题:
- 使用连接池对连接进行管理
- SQL和代码分离,集中管理
- 结果集映射
- 参数映射和动态SQL
- 重复SQL的提取
- 缓存管理
- 插件机制
Hibernate和MyBatis 跟 DbUtils、Spring JDBC一样,都是对JDBC的一个封装,我们去看源码,最后一定会看到Connection、Statement和ResultSet这些对象。
有这么多的工具和不同的框架,在实际的项目里面应该怎么选择?
在一些业务比较简单的项目中,我们可以使用Hibernate;
如果需要更加灵活的SQL,可以使用MyBatis,对于底层的编码,或者性能要求非常高的场合,可以用JDBC;
实际上在我们的项目中,MyBatis和Spring JDBC是可以混合使用的;
当然,我们也根据项目的需求自己写ORM框架。