1. 前言
MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。相对于Hibernate和ApacheOJB等“一站式”ORM解决方案而言,MyBatis是一款“半自动化”的ORM实现。
- 本文较为简略,主要是入门参考,深入学习请参考Mapper、PageHelper插件作者刘增辉书籍
2. MyBatis简介
2.1 三款框架的ORM对比图
以下针对Spring JDBC
、Spring Data Jpa
、Mybatis
**三款框架做了个粗略的对比。一般应用的性能瓶颈并不是在于ORM,所以这三个框架技术选型应该考虑项目的场景
、团队的技能掌握情况
、开发周期(开发效率)
…
框架对比 | Spring JDBC | Spring Data Jpa | Mybatis |
---|---|---|---|
性能 | 性能最好 | 性能最差 | 居中 |
代码量 | 多 | 少 | 多 |
学习成本 | 低 | 高 | 居中 |
推荐指数 | ❤❤❤ | ❤❤❤❤❤ | ❤❤❤❤❤ |
- 个人观点: 抛开学习成本而言,相对于中小型企业来说jpa开发无异于是最快速的,但鉴于国内市场环境而言,学习MyBatis是最佳选择。低学习成本和动态SQL的特点使得更容易被人们所接受。对于业务复杂且对性能要求较高的项目来说
Mybatis
往往能更好的胜任,可以自己进行SQL优化,同时更让我喜欢的是与有了这两款插件的支持,怎么能不选择MyBatis?
2.2 MyBatis
作为一款使用广泛的开源软件,它的特点有哪些呢?
优点
- SQL 被统一提取出来,便于统一管理和优化
- SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 灵活书写动态 SQL,支持各种条件来动态生成不同的 SQL
缺点
- 编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此
- SQL 语句依赖于数据库,导致数据库移植性差
MyBatis 几个重要的概念
Mapper 配置可以使用基于 XML 的 Mapper 配置文件来实现,也可以使用基于 Java 注解的 MyBatis 注解来实现,甚至可以直接使用 MyBatis 提供的 API 来实现。
Mapper 接口是指自行定义的一个数据操作接口,类似于通常所说的 DAO 接口。早期的 Mapper 接口需要自定义去实现,现在 MyBatis 会自动为 Mapper 接口创建动态代理对象。Mapper 接口的方法通常与 Mapper 配置文件中的 select、insert、update、delete 等 XML 结点存在一一对应关系。
Executor,MyBatis 中所有的 Mapper 语句的执行都是通过 Executor 进行的,Executor 是 MyBatis 的一个核心接口。
SqlSession,是 MyBatis 的关键对象,是执行持久化操作的独享,类似于 JDBC 中的 Connection,SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的方法,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行被映射的 SQL 语句。
SqlSessionFactory,是 MyBatis 的关键对象,它是单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory 对象的实例可以通过SqlSessionFactoryBuilder 对象类获得,而SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出。
MyBatis 的工作流程如下:
- 首先加载 Mapper 配置的 SQL 映射文件,或者是注解的相关 SQL 内容。
- 创建会话工厂,MyBatis 通过读取配置文件的信息来构造出会话工厂(SqlSessionFactory)。
- 创建会话。根据会话工厂,MyBatis 就可以通过它来创建会话对象(SqlSession),会话对象是一个接口,该接口中包含了对数据库操作的增、删、改、查方法。
- 创建执行器。因为会话对象本身不能直接操作数据库,所以它使用了一个叫做数据库执行器(Executor)的接口来帮它执行操作。
- 封装 SQL 对象。在这一步,执行器将待处理的 SQL 信息封装到一个对象中(MappedStatement),该对象包括 SQL 语句、输入参数映射信息(Java 简单类型、HashMap 或 POJO)和输出结果映射信息(Java 简单类型、HashMap 或 POJO)。
- 操作数据库。拥有了执行器和 SQL 信息封装对象就使用它们访问数据库了,最后再返回操作结果,结束流程。
在我们具体的使用过程中,就是按照上述的流程来执行。
3. springboot集成MyBatis
导入依赖
在pom.xml文件中添加MyBatis 依赖 MyBatis-Spring-Boot-Starter
org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.0 mysql mysql-connector-java 复制代码 org.springframework.boot spring-boot-starter-test test
注: 在IDEA新建项目时,可以直接勾选上MyBatis和MySQL,IDEA会自动导入依赖,但是在MySQL依赖中记得去除runtime,这个会导致你的MySQL驱动无法导入com.mysql.cj.jdbc.Driver报错。
mybatis-spring-boot-starter 是 MyBatis 帮助我们快速集成 Spring Boot 提供的一个组件包,使用这个组件可以做到以下几点:
- 构建独立的应用
- 几乎可以零配置
- 需要很少的 XML 配置
初始化脚本(创建表)
CREATE TABLE `t_user` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '主键自增', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(50) NOT NULL COMMENT '密码', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';复制代码
application 配置
application.yml 添加相关配置:
#数据库配置 spring: datasource: url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT&useSSL=true username: root password: rootroot driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:com/demo/mapper/*.xml type-aliases-package: com.demo.entity# 驼峰命名规范 如:数据库字段是 order_id 那么 实体字段就要写成 orderId configuration.map-underscore-to-camel-case: true复制代码
其中:
- mybatis.config-location,配置 mybatis-config.xml 路径,mybatis-config.xml 中配置 MyBatis 基础属性;
- mybatis.mapper-locations,配置 Mapper 对应的 XML 文件路径;
- mybatis.type-aliases-package,配置项目中实体类包路径;
- spring.datasource.*,数据源配置。
- application.properties 配置文件改成yml时,一定要把原来的properties 文件删掉再新建yml文件不然启动类会产生扫描找不到而报错。
- 上述项目目录如下图:
创建实体类
package com.demo.entity;import java.io.Serializable;/** * @Author: MaoLin * @Date: 2019/2/8 16:33 * @Version 1.0 */public class User implements Serializable { private static final long serialVersionUID = 8655851615465363473L; private Long id; private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}复制代码
持久层
MyBatis 以前只有 XML 配置这种使用的形式,到了后来注解使用特别广泛,MyBatis 也顺应潮流提供了注解的支持,从这里可以看出 MyBatis 一直都跟随着主流技术的变化来完善自己。接下来给大家介绍一下两种方法的使用。
UserMapper
package com.demo.mapper;import com.demo.entity.User;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import java.util.List;/** * @Author: MaoLin * @Date: 2019/2/8 16:35 * @Version 1.0 */@Mapperpublic interface UserMapper { /** * 注解方法 * 根据用户名查询用户结果集 * @param username 用户名 * @return 查询结果 */ @Select("SELECT * FROM t_user WHERE username = #{username}") ListfindByUsername(@Param("username") String username); /** * xml配置方法 * 保存用户信息 * @param user 用户信息 * @return 成功 1 失败 0 */ int insert(User user);}复制代码
UserMapper
映射文件
复制代码 INSERT INTO `t_user`(`username`,`password`) VALUES (#{username},#{password})
注:其中Select查询为注解的方法,不需要使用映射文件,直接注解就能使用,insert是采用的xml方法,SQL语句需要在映射文件中写,具体操作的使用,请参考。
测试
junit
测试类来检验代码的正确性。直接在IDEA项目的test测试文件里面操作即可。
package com.demo;import com.demo.entity.User;import com.demo.mapper.UserMapper;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.util.List;@RunWith(SpringRunner.class)@SpringBootTestpublic class DemoApplicationTests { private static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class); @Autowired private UserMapper userMapper; @Test public void test1() throws Exception { final int row1 = userMapper.insert(new User("u1", "p1")); log.info("[添加结果] - [{}]", row1); final int row2 = userMapper.insert(new User("u2","p2")); log.info("[添加结果] - [{}]", row2); final int row3 = userMapper.insert(new User("u1", "p3")); log.info("[添加结果] - [{}]", row3); final Listu1 = userMapper.findByUsername("u1"); log.info("[根据用户名查询] - [{}]", u1); }}复制代码
4. 通用Mapper与分页插件的集成
插件介绍
这两款插件作者均是同一个人,如果你想深入了解Mybatis
以及插件开发可以购买作者的书籍
分页插件
- GIT地址:
在没有分页插件之前,写一个分页需要两条SQL语句,一条查询一条统计,然后才能计算出页码,这样的代码冗余而又枯燥,更重要的一点是**数据库迁移
**,众所周知不同的数据库分页写法是不同的,而Mybatis
不同于Hibernate
的是它只提供动态SQL和结果集映射。值得庆幸的是,它虽然没有为分页提供良好的解决方案,但却提供了Interceptor
以供开发者自己扩展,这也是这款分页插件的由来….
通用Mapper
- GIT地址:
通用 Mapper
是一个可以实现任意 MyBatis
通用方法的框架,项目提供了常规的增删改查操作以及 Example
相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。
导入依赖
你只需要添加通用 Mapper 提供的 starter 就完成了最基本的集成,依赖如下:
复制代码 tk.mybatis mapper-spring-boot-starter 版本号
此时最新版本号2.1.5,你也可以从官方地址查看:
注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!
application 配置
# 通用mapper配置mapper: mappers: tk.mybatis.mapper.common.Mapper not-empty: false # 主键自增回写,默认为 MYSQL identity: MYSQL# 分页插件配置pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql复制代码
- 参数解释及其他参数
实体类
通用Mapper
采用了JPA规范包中的注解,这种的设计避免了重复造轮子,更是让Spring Data Jpa
的应用可以轻松切换到Mybatis
和上面实体大体一致,只是增加了注解。
package com.demo.entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;import java.io.Serializable;/** * @Author: MaoLin * @Date: 2019/2/8 16:33 * @Version 1.0 */@Table(name = "t_user")public class User implements Serializable { private static final long serialVersionUID = 8655851615465363473L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}复制代码
持久层
UserMapper
- 继承
BaseMapper<T>
就可以了,这点是不是有点类似JpaRepository
,同时也可以根据自己需要扩展出更适合自己项目的BaseMapper
,它的灵活也是众多开发者喜爱的因素之一
package com.demo.mapper;import com.demo.entity.User;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import tk.mybatis.mapper.common.BaseMapper;import java.util.List;/** * @Author: MaoLin * @Date: 2019/2/8 16:35 * @Version 1.0 */@Mapperpublic interface UserMapper extends BaseMapper{ /** * 根据用户名查询用户结果集 * * @param username 用户名 * @return 查询结果 */ @Select("SELECT * FROM t_user WHERE username = #{username}") List findByUsername(@Param("username") String username); /** * 保存用户信息 * * @param user 用户信息 * @return 成功 1 失败 0 */ @Insert("INSERT INTO `t_user`(`username`,`password`) VALUES (#{username},#{password})") int insert(User user); /** * 根据用户名统计(TODO 假设它是一个很复杂的SQL) * * @param username 用户名 * @return 统计结果 */ int countByUsername(String username);}复制代码
UserMapper
映射文件
复制代码
测试
package com.demo;import com.demo.entity.User;import com.demo.mapper.UserMapper;import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.util.List;@RunWith(SpringRunner.class)@SpringBootTestpublic class DemoApplicationTests { private static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class); @Autowired private UserMapper userMapper; @Test public void test1() throws Exception { final int row1 = userMapper.insert(new User("u1", "p1")); log.info("[添加结果] - [{}]", row1); final int row2 = userMapper.insert(new User("u2","p2")); log.info("[添加结果] - [{}]", row2); final int row3 = userMapper.insert(new User("u1", "p3")); log.info("[添加结果] - [{}]", row3); final Listu1 = userMapper.findByUsername("u1"); log.info("[根据用户名查询] - [{}]", u1); final User user1 = new User("u1", "p1"); final User user2 = new User("u1", "p2"); final User user3 = new User("u3", "p3"); userMapper.insertSelective(user1); log.info("[user1回写主键] - [{}]", user1.getId()); userMapper.insertSelective(user2); log.info("[user2回写主键] - [{}]", user2.getId()); userMapper.insertSelective(user3); log.info("[user3回写主键] - [{}]", user3.getId()); final int count = userMapper.countByUsername("u1"); log.info("[调用自己写的SQL] - [{}]", count); // TODO 模拟分页 for (int i = 0;i<10;i++){ userMapper.insertSelective(new User("u"+i, "p"+i)); } // TODO 分页 + 排序 this.userMapper.selectAll() 这一句就是我们需要写的查询,有了这两款插件无缝切换各种数据库 final PageInfo
结果
控制台结果:
5. 小结&参考资料
小结
要多查官方文档,一直在更新,你在使用的时候,可能有点已经不能再继续使用了,所以要多看官方文档。 本篇博客也只是简单入门,如果要深入研究,可以看一下作者的书籍。