MyBatis原理系列(一)-手把手带你阅读MyBatis源码
MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
MyBatis原理系列(七)-手把手带你了解如何自定义插件
MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制
作为Java码农,无论在面试中,还是在工作中都会遇到MyBatis的相关问题。笔者从大学开始就接触MyBatis,到现在为止都是会用,知道怎么配置,怎么编写xml,但是不知道Mybatis核心原理,一遇到问题就复制错误信息百度解决。为了改变这种境地,鼓起勇气开始下定决心阅读MyBatis源码,并开始记录阅读过程,希望和大家分享。
1. 初识MyBatis
还记得当初接触MyBatis时,觉得要配置很多,而且sql要单独写在xml中,相比Hibernate来说简直不太友好,直到后来出现了复杂的业务需求,需要编写相应的复杂的sql,此时用Hibernate反而更加麻烦了,用MyBatis是真香了。因此笔者对MyBatis的第一印象就是将业务关注的sql和java代码进行了解耦,在业务复杂变化的时候,相应的数据库操作需要相应进行修改,如果通过java代码构建操作数据逻辑,这不断变动的需求对程序员的耐心是极大的考验。如果将sql统一的维护在一个文件里,java代码用接口定义,在需求变动时,只用改相应的sql,从而减少了修改量,提高开发效率。以上也是经常在面试中经常问到的Hibernate和MyBatis间的区别一点。
切到正题,Mybatis是什么呢?
Mybatis SQL 映射框架使得一个面向对象构建的应用程序去访问一个关系型数据库变得更容易。MyBatis使用XML描述符或注解将对象与存储过程或SQL语句耦合。与对象关系映射工具相比,简单性是MyBatis数据映射器的最大优势。
以上是Mybatis的官方解释,其中“映射”,“面向对象”,“关系型”,“xml”等等都是Mybatis的关键词,也是我们了解了Mybatis原理后,会恍然大悟的地方。笔者现在不详述这些概念,在最后总结的时候再进行详述。我们只要知道Mybatis为我们操作数据库提供了很大的便捷。
2. 源码下载
我们首先要从github上下载源码,仓库地址,然后在IDEA中clone代码
在打开中的IDEA中,选择vsc -> get from version control -> 复制刚才的地址
点击clone即可
经过漫长的等待后,代码会全部下载下来,项目结果如下,框起来的就是我们要关注的核心代码了。
每个包就是MyBatis的一个模块,每个包的作用如下:
3. 一个简单的栗子
不知道现在还有没有同学知道怎么使用原生的JDBC进行数据库操作,现在框架太方便了,为我们考虑了很多,也隐藏了很多细节,因此会让我们处于一个云里雾里的境地,为什么这么设计,这样设计解决了什么问题,我们是不得而知的,为了了解其中奥秘,还是需要我们从头开始了解。
接下来笔者将以两个栗子来分别讲讲如何用原生的JDBC操作数据库,以及如何使用MyBatis框架来实现相同的功能,并比较两者的区别。
3.1 创建表
在此我们建了两张表,一张是t_test_user用户信息主表,一张是t_test_user_info用户信息副表,两张表通过member_id进行关联。
DROP TABLE IF EXISTS `t_test_user`;
CREATE TABLE `t_test_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`member_id` bigint(20) NOT NULL COMMENT '会员id',
`real_name` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '真实姓名',
`nickname` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '会员昵称',
`date_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`date_update` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bigint(20) DEFAULT '0' COMMENT '删除标识,0未删除,时间戳-删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42013 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';
DROP TABLE IF EXISTS `t_test_user_info`;
CREATE TABLE `t_test_user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`member_id` bigint(20) NOT NULL COMMENT '会员id',
`member_phone` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '电话',
`member_province` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '省',
`member_city` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '市',
`member_county` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '区',
`date_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`date_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bigint(20) NOT NULL DEFAULT '0' COMMENT '删除标识,0未删除,时间戳-删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户信息测试表';
3.2 使用Java JDBC进行操作数据库
JDBC(Java Database Connectivity,简称JDBC)是Java中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。使用JDBC操作数据库,一般包含7步,代码如下。
public class JDBCTest {
/**
* 数据库地址 替换成本地的地址
*/
private static final String url = "jdbc:mysql://localhost:3306/test?useUnicode=true";
/**
* 数据库用户名
*/
private static final String username = "test";
/**
* 密码
*/
private static final String password = "test";
public static void main(String[] args) {
try {
// 1. 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获得连接
Connection connection = DriverManager.getConnection(url, username, password);
// 3. 创建sql语句
String sql = "select * from t_test_user";
Statement statement = connection.createStatement();
// 4. 执行sql
ResultSet result = statement.executeQuery(sql);
// 5. 处理结果
while(result.next()){
System.out.println("result = " + result.getString(1));
}
// 6. 关闭连接
result.close();
connection.close();
} catch (Exception e){
System.out.println(e);
}
}
}
3.3 使用Mybatis进行操作数据库
3.3.1 新增mybatis-config.xml配置
在路径src/main/resources/mybatis-config.xml新增配置,配置内容如下
-->
3.3.2 新增mapper接口
新增src/main/java/com/example/demo/dao/TTestUserMapper.java 接口
package com.example.demo.dao;
import com.example.demo.entity.TTestUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TTestUserMapper {
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
int deleteByPrimaryKey(Long id);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
int insert(TTestUser record);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
int insertSelective(TTestUser record);
int batchInsert(List records);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
TTestUser selectByPrimaryKey(Long id);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
int updateByPrimaryKeySelective(TTestUser record);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
int updateByPrimaryKey(TTestUser record);
}
3.3.3 新增映射配置文件
src/main/resources/mapper/TTestUserMapper.xml 新增映射配置文件
id, member_id, real_name, nickname, date_create, date_update, deleted
delete from t_test_user
where id = #{id,jdbcType=BIGINT}
insert into t_test_user (id, member_id, real_name,
nickname, date_create, date_update,
deleted)
values (#{id,jdbcType=BIGINT}, #{memberId,jdbcType=BIGINT}, #{realName,jdbcType=VARCHAR},
#{nickname,jdbcType=VARCHAR}, #{dateCreate,jdbcType=TIMESTAMP}, #{dateUpdate,jdbcType=TIMESTAMP},
#{deleted,jdbcType=BIGINT})
insert into t_test_user
id,
member_id,
real_name,
nickname,
date_create,
date_update,
deleted,
#{id,jdbcType=BIGINT},
#{memberId,jdbcType=BIGINT},
#{realName,jdbcType=VARCHAR},
#{nickname,jdbcType=VARCHAR},
#{dateCreate,jdbcType=TIMESTAMP},
#{dateUpdate,jdbcType=TIMESTAMP},
#{deleted,jdbcType=BIGINT},
update t_test_user
member_id = #{memberId,jdbcType=BIGINT},
real_name = #{realName,jdbcType=VARCHAR},
nickname = #{nickname,jdbcType=VARCHAR},
date_create = #{dateCreate,jdbcType=TIMESTAMP},
date_update = #{dateUpdate,jdbcType=TIMESTAMP},
deleted = #{deleted,jdbcType=BIGINT},
where id = #{id,jdbcType=BIGINT}
update t_test_user
set member_id = #{memberId,jdbcType=BIGINT},
real_name = #{realName,jdbcType=VARCHAR},
nickname = #{nickname,jdbcType=VARCHAR},
date_create = #{dateCreate,jdbcType=TIMESTAMP},
date_update = #{dateUpdate,jdbcType=TIMESTAMP},
deleted = #{deleted,jdbcType=BIGINT}
where id = #{id,jdbcType=BIGINT}
3.3.5 新增实体类
package com.example.demo.entity;
import java.io.Serializable;
import java.util.Date;
public class TTestUser implements Serializable {
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.id
*
* @mbggenerated
*/
private Long id;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.member_id
*
* @mbggenerated
*/
private Long memberId;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.real_name
*
* @mbggenerated
*/
private String realName;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.nickname
*
* @mbggenerated
*/
private String nickname;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.date_create
*
* @mbggenerated
*/
private Date dateCreate;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.date_update
*
* @mbggenerated
*/
private Date dateUpdate;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_test_user.deleted
*
* @mbggenerated
*/
private Long deleted;
/**
* This field was generated by MyBatis Generator.
* This field corresponds to the database table t_test_user
*
* @mbggenerated
*/
private static final long serialVersionUID = 1L;
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.id
*
* @return the value of t_test_user.id
*
* @mbggenerated
*/
public Long getId() {
return id;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.id
*
* @param id the value for t_test_user.id
*
* @mbggenerated
*/
public void setId(Long id) {
this.id = id;
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.member_id
*
* @return the value of t_test_user.member_id
*
* @mbggenerated
*/
public Long getMemberId() {
return memberId;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.member_id
*
* @param memberId the value for t_test_user.member_id
*
* @mbggenerated
*/
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.real_name
*
* @return the value of t_test_user.real_name
*
* @mbggenerated
*/
public String getRealName() {
return realName;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.real_name
*
* @param realName the value for t_test_user.real_name
*
* @mbggenerated
*/
public void setRealName(String realName) {
this.realName = realName == null ? null : realName.trim();
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.nickname
*
* @return the value of t_test_user.nickname
*
* @mbggenerated
*/
public String getNickname() {
return nickname;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.nickname
*
* @param nickname the value for t_test_user.nickname
*
* @mbggenerated
*/
public void setNickname(String nickname) {
this.nickname = nickname == null ? null : nickname.trim();
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.date_create
*
* @return the value of t_test_user.date_create
*
* @mbggenerated
*/
public Date getDateCreate() {
return dateCreate;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.date_create
*
* @param dateCreate the value for t_test_user.date_create
*
* @mbggenerated
*/
public void setDateCreate(Date dateCreate) {
this.dateCreate = dateCreate;
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.date_update
*
* @return the value of t_test_user.date_update
*
* @mbggenerated
*/
public Date getDateUpdate() {
return dateUpdate;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.date_update
*
* @param dateUpdate the value for t_test_user.date_update
*
* @mbggenerated
*/
public void setDateUpdate(Date dateUpdate) {
this.dateUpdate = dateUpdate;
}
/**
* This method was generated by MyBatis Generator.
* This method returns the value of the database column t_test_user.deleted
*
* @return the value of t_test_user.deleted
*
* @mbggenerated
*/
public Long getDeleted() {
return deleted;
}
/**
* This method was generated by MyBatis Generator.
* This method sets the value of the database column t_test_user.deleted
*
* @param deleted the value for t_test_user.deleted
*
* @mbggenerated
*/
public void setDeleted(Long deleted) {
this.deleted = deleted;
}
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table t_test_user
*
* @mbggenerated
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", memberId=").append(memberId);
sb.append(", realName=").append(realName);
sb.append(", nickname=").append(nickname);
sb.append(", dateCreate=").append(dateCreate);
sb.append(", dateUpdate=").append(dateUpdate);
sb.append(", deleted=").append(deleted);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
3.3.6 执行查询
@Slf4j
public class MyBatisBootStrap {
public static void main(String[] args) {
try {
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 执行接口方法
TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
// 6. 提交事物
sqlSession.commit();
// 7. 关闭资源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
}
}
3.4 区别
发现没有在写MyBatis的时候,新增了dao, mapper.xml, entity, mybatis-config.xml等很多东西,工作量反而增大了。但是dao, mapper.xml, entity都是可以根据插件mybatis-generator生成的,我们也不用一一去创建,而且我们没有涉及到原生JDBC中加载驱动,创建连接,处理结果集,关闭连接等等这些操作,这些都是MyBatis帮我们做了,我们只用关心提供的查询接口和sql编写即可。
如果使用原生的JDBC进行数据库操作,我们需要关心如何加载驱动,如何获取连接关闭连接,如何获取结果集等等与业务无关的地方,而MyBatis通过“映射”这个核心概念将sql和java接口关联起来,我们调用java接口就相当于可以直接执行sql,并且将结果映射为java pojo对象,这也是我们开头说的“映射”,“面向对象的”的原因了。
4. 总结
这篇文章简单的介绍了下MyBatis的基本概念,并提供了简单的栗子,接下来几篇文章打算写下Mybatis的启动流程,让我们更好的了解下mybatis的各模块协作。