MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。相对于Hibernate和ApacheOJB等“一站式”ORM解决方案而言,MyBatis是一款“半自动化”的ORM实现。
以下针对Spring JDBC
、Spring Data Jpa
、Mybatis
**三款框架做了个粗略的对比。一般应用的性能瓶颈并不是在于ORM,所以这三个框架技术选型应该考虑项目的场景
、团队的技能掌握情况
、开发周期(开发效率)
…
框架对比 | Spring JDBC | Spring Data Jpa | Mybatis |
---|---|---|---|
性能 | 性能最好 | 性能最差 | 居中 |
代码量 | 多 | 少 | 多 |
学习成本 | 低 | 高 | 居中 |
推荐指数 | ❤❤❤ | ❤❤❤❤❤ | ❤❤❤❤❤ |
Mybatis
往往能更好的胜任,可以自己进行SQL优化,同时更让我喜欢的是Mybatis分页插件与通用Mapper(单表CURD无需自己手写)有了这两款插件的支持,怎么能不选择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 的工作流程如下:
在我们具体的使用过程中,就是按照上述的流程来执行。
在pom.xml文件中添加MyBatis 依赖 MyBatis-Spring-Boot-Starter
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
注: 在IDEA新建项目时,可以直接勾选上MyBatis和MySQL,IDEA会自动导入依赖,但是在MySQL依赖中记得去除runtime,这个会导致你的MySQL驱动无法导入com.mysql.cj.jdbc.Driver报错。
mybatis-spring-boot-starter 是 MyBatis 帮助我们快速集成 Spring Boot 提供的一个组件包,使用这个组件可以做到以下几点:
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.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
其中:
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
*/
@Mapper
public interface UserMapper {
/**
* 注解方法
* 根据用户名查询用户结果集
* @param username 用户名
* @return 查询结果
*/
@Select("SELECT * FROM t_user WHERE username = #{username}")
List<User> findByUsername(@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语句需要在映射文件中写,具体操作的使用,请参考mybatis 官方文档。
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)
@SpringBootTest
public 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 List<User> u1 = userMapper.findByUsername("u1");
log.info("[根据用户名查询] - [{}]", u1);
}
}
这两款插件作者均是同一个人,如果你想深入了解Mybatis
以及插件开发可以购买作者的书籍
分页插件
在没有分页插件之前,写一个分页需要两条SQL语句,一条查询一条统计,然后才能计算出页码,这样的代码冗余而又枯燥,更重要的一点是**数据库迁移
**,众所周知不同的数据库分页写法是不同的,而Mybatis
不同于Hibernate
的是它只提供动态SQL和结果集映射。值得庆幸的是,它虽然没有为分页提供良好的解决方案,但却提供了Interceptor
以供开发者自己扩展,这也是这款分页插件的由来….
通用Mapper
通用 Mapper
是一个可以实现任意 MyBatis
通用方法的框架,项目提供了常规的增删改查操作以及 Example
相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。
你只需要添加通用 Mapper 提供的 starter 就完成了最基本的集成,依赖如下:
tk.mybatis
mapper-spring-boot-starter
版本号
此时最新版本号2.1.5,你也可以从官方地址查看:
http://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter
注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!
# 通用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
就可以了,这点是不是有点类似 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
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户名查询用户结果集
*
* @param username 用户名
* @return 查询结果
*/
@Select("SELECT * FROM t_user WHERE username = #{username}")
List<User> 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)
@SpringBootTest
public 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 List<User> u1 = 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<Object> pageInfo = PageHelper.startPage(1, 10).setOrderBy("id desc").doSelectPageInfo(() -> this.userMapper.selectAll());
log.info("[lambda写法] - [分页信息] - [{}]", pageInfo.toString());
PageHelper.startPage(1, 10).setOrderBy("id desc");
final PageInfo<User> userPageInfo = new PageInfo<>(this.userMapper.selectAll());
log.info("[普通写法] - [{}]", userPageInfo);
}
}
控制台结果:
要多查官方文档,一直在更新,你在使用的时候,可能有点已经不能再继续使用了,所以要多看官方文档。
本篇博客也只是简单入门,如果要深入研究,可以看一下作者的书籍。
源码GIT地址:https://github.com/chapaofan/MyBatis