目录
Mybatis结构
MyBatis分页方式
逻辑分页
物理分页
分页插件(PageHelper)
Mybatis代码生成器
添加依赖
新建工具类
Mybatis通用方法
Mybatis初始化
BaseBuilder
XMLConfigBuilder
XMLMapperBuilder
XMLStatementBuilder
绑定 Mapper 接口
生如蝼蚁 当立鸿鹄之志 命薄如纸 应有不屈之心 大丈夫生于天地间 岂能郁郁久居人下 乾坤未定 你我皆是黑马
iBATIS
iBATIS一词来源于"internet"和"abatis"的组合,是apache的一个开源项目(由Clinton Begin在2001年发起的开放源代码项目)。 于2010年6月16号这个项目由apache software foundation 迁移到了google code(被谷歌托管)并且改名为MyBatis ,2013年11月迁移到Github。是一个基于SQL映射支持Java和.NET的持久层框架,使用XML或注解来配置和映射原生信息,将接口和Java的POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录。查看ibatis官网:
iBATIS HomeiBATIS Data Mapping Framework for Java and .NEThttps://ibatis.apache.org/会发现有如下提示:
Mybatis
mybatis是一个优秀的基于java语言开发的持久层开源框架,内部封装jdbc,开发者只需要关注sql 语句本身,不需要加载驱动、创建连接、创建statement等操作。通过xml文件或注解的方式将要执行的各种statement配置起来,然后java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,由mybatis框架执行sql并将结果映射为java对象并返回。采用ORM(Object relational mapping)思想解决了实体和数据库映射的问题,对jdbc的封装,屏蔽了jdbc api底层访问细节,不用与jdbc api打交道就可以完成对数据库的持久化操作,简化开发。以前用JDBC是这样的:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager
.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", " root");
//定义 sql 语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始)
//第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出 sql 执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id")+""
+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
数据库连接创建、连接释放操作频繁会造成系统资源的浪费影响系统性能,使用数据库连接池解决了此问题。Sql语句在代码中硬编码,代码不易维护。例如结果集解析(查询列名)sql变化导致解析代码变更。Mybatis内置数据库连接池、提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试、提供XML标签,支持编写动态sql,sql语句写在xml文件里面,便于统一进行管理。
mybatis – MyBatis 3 | Introductionhttps://mybatis.org/mybatis-3/
Mybatis-Plus
MyBatis-Plus (opens new window)(简称 MP)在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis-PlusMyBatis-Plus 官方文档https://baomidou.com/
1、无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作BaseMapper。
2、强大的CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD操作,更有强大的条件构造器,满足各类使用需求,简单的CRUD操作不用自己编写。
3、支持Lambda形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。
4、支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题。
5、支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。
6、支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
7、内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(自动生成代码)。
8、内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询。
9、分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库。
10、内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。
11、内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。
Mybatis持久层框架具有轻量级、解耦(业务逻辑和数据访问分离,使系统结构更清晰,易维护,更易单元测试)的特点,分为以下几层:
API接口层:提供一些API接口,通过这些API接口来操纵数据库,接口层收到调用请求会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等,主要目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的功能,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。
IBatis与MyBatis
在sqlMap里面,iBatis的传入参数是parameterClass,而MyBatis是可以不写的,也可以用parameterType,iBatis的传出参数是resultClass
iBatis:
MyBatis:
条件判断语句对于MyBatis很简单,标签里面写判断条件即可。但是IBatis就麻烦了许多,它将每个方法都进行了封装
例如:
isNull:判断property字段是否是null
isEqual相当于equals,判断状态值。
或
isEmpty判断参数是否为Null或者空,满足其中一个条件则其true。
isNotEmpty相反,当参数既不为Null也不为空时其为true。
Ibatis:
username=#userNameList[]#
Mybaits:
#{item.id}
#{}和${}
select * from user where name = #{name};
select * from user where name = ${name};
//解析后结果为:
select * from user where name = 'zhangsan';
//#{}和${}在预编译中的处理是不一样的,#{}在预处理时会把参数部分用一个占位符?代替,变成:
select * from user where name = ?;
//${}则只是简单的字符串替换,在动态解析阶段sql语句会被解析成:
select * from user where name = 'zhangsan';
//#{}的参数替换是发生在DBMS中,而${}发生在动态解析过程中,${}会导致sql注入的问题。
MyBatis和Hibernate
灵活性
MyBatis操作灵活,使用起来比较方便。hibernate是全自动,而mybatis是半自动。hibernate可以通过对象关系模型实现对数据库的操作,通过实体对象与数据库的表进行映射来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理
可移植性
Hibernate数据库移植性远大于Mybatis。hibernate通过它强大的映射结构和hql语言,大大降低了对象与不同数据库(oracle、MySQL等)的耦合性,而mybatis由于需要手写sql,因此sql中很容易包含一些不同的数据库不兼容的函数或者语法,移植性也会随之降低很多,成本很高.
优化上
在sql优化上,mybatis要比hibernate方便一些,由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,像报表等需求时,hql满足不了需求;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上hibernate不及mybatis。
二级缓存
hibernate拥有更好的二级缓存,它的二级缓存可以自行更换为第三方的二级缓存。
使用MyBatis自带的RowBounds进行分页,一次性查询很多数据,然后在结果中检索分页的数据。这样弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。 RowBounds不是一次性查询全部结果,因为MyBatis是对jdbc的封装,在jdbc驱动中有一个Fetch Size的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行next()的时候,去查询更多的数据。
自己手写SQL分页或使用分页插件PageHelper,从数据库查询指定条数的数据。
原理
分页插件的基本原理是使用MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数。
//maven依赖
com.github.pagehelper
pagehelper
5.1.8
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.10
分页
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
PageHelper.startPage(1,10,true); //第一页 每页显示10条
Page page=userMapper.findUserAll();
不分页
PageHelper.startPage(1,-1,true);
查询总条数
PageInfo info=new PageInfo<>(userMapper.findUserAll());
com.baomidou
mybatis-plus-generator
3.3.0
mysql
mysql-connector-java
5.1.25
org.apache.velocity
velocity-engine-core
2.0
public class Generator {
public static void main(String[] args) throws IOException {
AutoGenerator mpg = new AutoGenerator();// 代码生成器
GlobalConfig gc = new GlobalConfig(); // 全局配置
String projectPath = "D:\\parkson-platform-dev\\account"; //当前项目路径
gc.setOutputDir(projectPath + "/src/main/java");//输出路径
gc.setAuthor("parkson");//作者
gc.setOpen(false);
gc.setSwagger2(true);//实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
DataSourceConfig dsc = new DataSourceConfig();// 数据源配置
//设置数据库地址
dsc.setUrl("jdbc:mysql://200.86.3.85:8003/parkson_account_dev?serverTimezone=UTC&characterEncoding=UTF-8");
//dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("parkson1016");
mpg.setDataSource(dsc);
PackageConfig pc = new PackageConfig();// 包配置
// pc.setModuleName(scanner("模块名"));
pc.setModuleName("");
pc.setParent("com.parkson.platform.account");//生成的代码路径
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 配置自定义输出模板
String templatePath = "/templates/mapper.xml.vm";
List focList = new ArrayList<>();
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名
return projectPath + "/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);;
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
strategy.setInclude("t_vcard");
mpg.setStrategy(strategy);
//mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute();
}
}
示例2:
com.oracle
ojdbc7
12.1.0.2
/**
* Oracle数据库
* mybatis-plus代码生成器
*/
public class Generator {
public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir("E:\\Mybatis_Plus");// 生产文件所在的目录
gc.setFileOverride(true); // 默认 false ,是否覆盖已生成文件
gc.setOpen(false); //默认true ,是否打开文件生产所在的目录
gc.setIdType(IdType.ASSIGN_UUID); // 指定生成的主键类型
gc.setActiveRecord(true);
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(false);// XML columList
gc.setAuthor("[email protected]");
// 生成文件命名('%s'会自动填充表名)
gc.setMapperName("%sMapper");
gc.setXmlName("%sMapper");
gc.setServiceName("%sService");
gc.setServiceImplName("%sServiceImpl");
gc.setControllerName("%sController");
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.ORACLE);
dsc.setDriverName("oracle.jdbc.driver.OracleDriver");
dsc.setUsername("qms");
dsc.setPassword("csotqms1129");
dsc.setUrl("jdbc:oracle:thin:@10.108.234.130:1521:d2pidb1");
mpg.setDataSource(dsc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
//strategy.setTablePrefix(new String[]{"D_"});// 设置表前缀
strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
strategy.setInclude(new String[]{"QMS_LAB_APP_RESULTS"}); // 需要生成的表
mpg.setStrategy(strategy);
// 文件生产所在目录配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.csot.qms");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setXml("mapper");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 注入自定义配置,可以在VM中使用cfg.abc设置的值
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
}
};
// 配置模板
TemplateConfig tc = new TemplateConfig();
// templates/entity.java 模板路径配置,默认在templates目录下,.vm 后缀不用加
tc.setEntity("templates/entity.java");//使用自定义模板生成实体类
tc.setXml("");
//mpg.setTemplate(tc);
mpg.setCfg(cfg);
// 执行生成
mpg.execute();
// 打印注入设置
//System.err.println(mpg.getCfg().getMap().get("abc"));
}
}
tk.mybatis包提供通用的增删改查的方法
tk.mybatis
mapper
4.0.4
更新
根据主键更新数据,更新NULL的字段,
updateByPrimaryKey(T var1)
根据主键更新有值的数据,
updateByPrimaryKeySelective(T var1)
根据Condition条件更新实体record
包含的全部属性,null值会被更新,
updateByCondition(@Param("record") T var1, @Param("condition") Object var2)
根据Condition条件更新实体record
包含的不是null的属性值,
updateByConditionSelective(@Param("record") T var1, @Param("condition") Object var2)
根据Example条件更新实体record
包含的不是null的属性值,
updateByExampleSelective(@Param("record") T var1, @Param("example") Object var2)
根据Example条件更新实体record
包含的全部属性,null值会被更新,
updateByExample(@Param("record") T var1, @Param("example") Object var2)
根据主键批量修改。
updateListByPrimaryKey(List extends T> recordList);
根据指定条件更新指定字段的情况上面几种方法可能都不满足,就只能自己写sql了。
updateStatusByPrimaryKey(InsureOrgSchemesInfo insureOrgSchemesInfo);
update INSURE_ORG_SCHEMES_INFO a
a.state = #{state}
where a.id = #{id}
插入
批量新增
insertList(List extends T> var1):
所有的字段都会添加一遍,即使有的字段没有值
insert(T var1)
只给有值的字段赋值(会对传进来的值做非空判断)
insertSelective(T var1)
insert和insertSelective在数据库中的效果是一样的,只是sql语句不同。
insertUseGeneratedKeys():
官网文档:MySQL :: MySQL 5.6 Reference Manual :: 13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement
删除
delete(T var1);
DELETE FROM db_test.t_test_table
AND id = #{id}
AND col = #{col}
deleteByPrimaryKey(Object var1):
DELETE FROM db_test.t_test_table
AND id = #{id}
根据条件删除
deleteByExample(Object var1)
Example example = new Example(User.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("name","王小二");//相当于 where name = "王小二"
deleteByExample(example );
查询
根据条件查询,作非空判断。
select(T var1)
根据条件查询 查询行数
selectCount(T var1)
查询全部
selectAll()
根据Example条件查询
selectByExample(Object var1)
根据Example条件查询行数
selectCountByExample(Object var1)
根据条件查询一个
selectOne(T var1)
selectOneByExample(Object var1)
根据example条件和RowBounds进行分页查询
selectByExampleAndRowBounds(Object var1, RowBounds var2)
根据实体条件和RowBounds进行分页查询
selectByRowBounds(T var1, RowBounds var2)
根据主键查询
selectByPrimaryKey(Object var1)
和Spring框架的IoC容器初始化 一样,Mybatis也会通过解析相应的配置文件完成自己的初始化。Mybatis 的配置文件主要有 mybatis-config.xml 核心配置文件 及一系列映射配置文件,另外,Mybatis 也会根据注解进行配置。
Mybatis 初始化 的主要内容是加载并解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。Mybatis 的初始化入口是 SqlSessionFactoryBuilder 的 build()方法。
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
/**
* build()方法 的主要实现
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// SqlSessionFactory 会创建 XMLConfigBuilder对象 来解析 mybatis-config.xml配置文件
// XMLConfigBuilder 继承自 BaseBuilder抽象类,顾名思义这一系的类使用了 建造者设计模式
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件的内容 到 Configuration对象,根据 Configuration对象
// 创建 DefaultSqlSessionFactory对象,然后返回
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
// 关闭配置文件输入流
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
BaseBuilder
public abstract class BaseBuilder {
// 保存了 Mybatis 的几乎所以核心配置信息,全局唯一
protected final Configuration configuration;
// 在 mybatis-config.xml 中可以通过 标签 定义别名
protected final TypeAliasRegistry typeAliasRegistry;
// 在 mybatis-config.xml 中可以通过 标签 添加 自定义TypeHandler
// TypeHandler 用于完成 JDBC数据类型 与 Java类型 的相互转换,所有的 TypeHandler
// 都保存在 typeHandlerRegistry 中
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
BaseBuilder 中的 typeAliasRegistry 和 typeHandlerRegistry 字段 均来自于 configuration,通过 BaseBuilder 的构造方法可以看到详细内容
XMLConfigBuilder 是 BaseBuilder 的众多子类之一,主要负责解析 mybatis-config.xml 配置文件。它通过调用 parseConfiguration()方法 实现整个解析过程,其中,mybatis-config.xml 配置文件 中的每个节点都被封装成了一个个相应的解析方法,parseConfiguration()方法 只是依次调用了这些解析方法而已。
public class XMLConfigBuilder extends BaseBuilder {
// 标记是否解析过 mybatis-config.xml文件
private boolean parsed;
// 用于解析 mybatis-config.xml 的解析器
private final XPathParser parser;
// 标识 配置 的名称,默认读取 标签 的 default属性
private String environment;
// 创建并缓存 Reflector对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
/**
* 解析的入口,调用了 parseConfiguration() 进行后续的解析
*/
public Configuration parse() {
// parsed标志位 的处理
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在 mybatis-config.xml配置文件 中查找 节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 根据 root.evalNode("properties") 中的值就可以知道具体是解析哪个标签的方法咯
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
解析
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
// 处理 下的所有子标签
for (XNode child : parent.getChildren()) {
// 处理 标签
if ("package".equals(child.getName())) {
// 获取指定的包名
String typeHandlerPackage = child.getStringAttribute("name");
// 通过 typeHandlerRegistry 的 register(packageName)方法
// 扫描指定包中的所有 TypeHandler类,并进行注册
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// Java数据类型
String javaTypeName = child.getStringAttribute("javaType");
// JDBC数据类型
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class> typeHandlerClass = resolveClass(handlerTypeName);
// 注册
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
解析
/**
* Mybatis 可以配置多个 环境,分别用于开发、测试及生产等,
* 但每个 SqlSessionFactory实例 只能选择其一
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 如果未指定 XMLConfigBuilder 的 environment字段,则使用 default属性 指定的 环境
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历 节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 实例化 TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建 DataSourceFactory 和 DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建的 Environment对象 中封装了上面的 TransactionFactory对象 和 DataSource对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 为 configuration 注入 environment属性值
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析
Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql 语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql 语句。但在 mybatis-config.xml 配置文件 中,可以通过
/**
* 解析 节点,并创建指定的 DatabaseIdProvider对象,
* 该对象会返回 databaseId的值,Mybatis 会根据 databaseId 选择对应的 sql语句 去执行
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// 为了保证兼容性,修改 type取值
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 解析相关配置信息
Properties properties = context.getChildrenAsProperties();
// 创建 DatabaseIdProvider对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 配置 DatabaseIdProvider,完成初始化
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 根据前面解析到的 DataSource 获取 databaseId,并记录到 configuration 的 configuration属性 上
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
Mybatis 提供了 DatabaseIdProvider 接口,该接口的核心方法为 getDatabaseId(DataSource dataSource),主要根据 dataSource 查找对应的 databaseId 并返回。该接口的主要实现类为 VendorDatabaseIdProvider。
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
private static final Log log = LogFactory.getLog(VendorDatabaseIdProvider.class);
private Properties properties;
@Override
public void setProperties(Properties p) {
this.properties = p;
}
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
// 解析到数据库产品名
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
// 根据 子节点 配置的数据库产品和 databaseId 之间的对应关系,
// 确定最终使用的 databaseId
for (Map.Entry
解析
Mybatis 初始化时,除了加载 mybatis-config.xml 文件,还会加载全部的映射配置文件,mybatis-config.xml 文件的
/**
* 解析 节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在
* 相应的 Mapper接口,也会加载相应的 Mapper接口,解析其中的注解 并完成向 MapperRegistry 的注册
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理 的子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获取 子节点 中的包名
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包目录,然后向 MapperRegistry 注册 Mapper接口
configuration.addMappers(mapperPackage);
} else {
// 获取 节点 的 resource、url、mapperClass属性,这三个属性互斥,只能有一个不为空
// Mybatis 提供了通过包名、映射文件路径、类全名、URL 四种方式引入映射器。
// 映射器由一个接口和一个 XML配置文件 组成,XML文件 中定义了一个 命名空间namespace,
// 它的值就是接口对应的全路径。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果 节点 指定了 resource 或是 url属性,则创建 XMLMapperBuilder对象 解析
// resource 或是 url属性 指定的 Mapper配置文件
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 如果 节点 指定了 class属性,则向 MapperRegistry 注册 该Mapper接口
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
和 XMLConfigBuilder 一样,XMLMapperBuilder 也继承了 BaseBuilder,其主要负责解析映射配置文件,其解析配置文件的入口方法也是 parse(),另外,XMLMapperBuilder 也将各个节点的解析过程拆分成了一个个小方法,然后由 configurationElement()方法 统一调用
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 是否已经加载过该配置文件
if (!configuration.isResourceLoaded(resource)) {
// 解析 节点
configurationElement(parser.evalNode("/mapper"));
// 将 resource 添加到 configuration 的 loadedResources属性 中,
// 该属性是一个 HashSet类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 注册 Mapper接口
bindMapperForNamespace();
}
// 处理 configurationElement()方法 中解析失败的 节点
parsePendingResultMaps();
// 处理 configurationElement()方法 中解析失败的 节点
parsePendingCacheRefs();
// 处理 configurationElement()方法 中解析失败的 节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取 节点 的 namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 使用 MapperBuilderAssistant对象 的 currentNamespace属性 记录 namespace命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析 节点,后面的解析方法 也都见名知意
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的
解析
select 语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言,对象是根据类的定义创建的,类之间的引用关系可以认为是嵌套结构。JDBC 编程 中,为了将结果集中的数据映射成 VO 对象,我们需要自己写代码从结果集中获取数据,然后将数据封装成对应的 VO 对象,并设置好对象之间的关系,这种 ORM 的过程中存在大量重复的代码。
Mybatis 通过
在分析
public class ResultMapping {
private Configuration configuration;
// 对应节点的 property属性,表示 该列进行映射的属性
private String property;
// 对应节点的 column属性,表示 从数据库中得到的列名或列名的别名
private String column;
// 表示 一个 JavaBean 的完全限定名,或一个类型别名
private Class> javaType;
// 进行映射列的 JDBC类型
private JdbcType jdbcType;
// 类型处理器
private TypeHandler> typeHandler;
// 该属性通过 id 引用了另一个 节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过 join方式 进行关联查询,然后直接映射成
// 多个对象,并同时设置这些对象之间的组合关系(nested嵌套的)
private String nestedResultMapId;
// 该属性通过 id 引用了另一个
另一个比较重要的类是 ResultMap,每个
public class ResultMap {
private Configuration configuration;
// 这些属性一一对应了 中的属性
private String id;
private Class> type;
// 记录了除 节点 之外的其它映射关系(即,ResultMapping对象集合)
private List resultMappings;
// 记录了映射关系中带有 ID标志 的映射关系,如:节点 和 节点 的 子节点
private List idResultMappings;
// 记录了映射关系中带有 Constructor标志 的映射关系,如:所有子元素
private List constructorResultMappings;
// 记录了映射关系中不带有 Constructor标志 的映射关系
private List propertyResultMappings;
// 记录了所有映射关系中涉及的 column属性 的集合
private Set mappedColumns;
// 记录了所有映射关系中涉及的 property属性 的集合
private Set mappedProperties;
// 鉴别器,对应 节点
private Discriminator discriminator;
// 是否含有嵌套的结果映射,如果某个映射关系中存在 resultMap属性,
// 且不存在 resultSet属性,则为true
private boolean hasNestedResultMaps;
// 是否含有嵌套查询,如果某个属性映射存在 select属性,则为true
private boolean hasNestedQueries;
// 是否开启自动映射
private Boolean autoMapping;
}
了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections. emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 的 id属性,默认值会拼装所有父节点的 id 或 value 或 property属性值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 的 type属性,表示结果集将被映射成 type 指定类型的对象
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 该属性指定了该 节点 的继承关系
String extend = resultMapNode.getStringAttribute("extends");
// 为 true 则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用 setter方法,
// 为 false,则需要在 节点 内注明映射关系才会调用对应的 setter方法
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析 type类型
Class> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 该集合用来记录解析结果
List resultMappings = new ArrayList();
resultMappings.addAll(additionalResultMappings);
// 获取并处理 的子节点
List resultChildren = resultMapNode.getChildren();
// child 单数形式,children 复数形式
for (XNode resultChild : resultChildren) {
// 处理 节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
// 处理 节点
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理 , , , 等节点
List flags = new ArrayList();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建 ResultMapping对象,并添加到 resultMappings集合
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
Mybatis 从
/**
* 根据上下文环境构建 ResultMapping
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class> resultType, List flags) throws Exception {
// 获取各个节点的属性,见文知意
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections. emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建 ResultMapping对象 并返回
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
得到 ResultMapping 对象集合 之后,会调用 ResultMapResolver 的 resolve()方法,该方法会调用 MapperBuilderAssistant 的 addResultMap()方法 创建 ResultMap 对象,并将 ResultMap 对象 添加到 Configuration 的 resultMaps 集合 中保存。
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class> type, String extend,
Discriminator discriminator, List resultMappings, Boolean autoMapping) {
// ResultMap 的 完整id 是 "namespace.id" 的格式
id = applyCurrentNamespace(id, false);
// 获取 父ResultMap 的 完整id
extend = applyCurrentNamespace(extend, true);
// 针对 extend属性 进行的处理
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 父ResultMap对象
ResultMap resultMap = configuration.getResultMap(extend);
// 父ResultMap对象 的 ResultMapping集合
List extendedResultMappings = new ArrayList(resultMap.getResultMappings());
// 删除需要覆盖的 ResultMapping集合
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
// 添加需要被继承下来的 ResultMapping集合
resultMappings.addAll(extendedResultMappings);
}
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}
}
解析
在映射配置文件中,可以使用
private void sqlElement(List list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List list, String requiredDatabaseId) throws Exception {
// 遍历 节点
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 为 id 添加命名空间
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测 的 databaseId 与当前 Configuration 中记录的 databaseId 是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 记录到 sqlFragments(Map) 中保存
sqlFragments.put(id, context);
}
}
}
通过之前对 binding 模块 的解析可知,每个映射配置文件的命名空间可以绑定一个 Mapper 接口,并注册到 MapperRegistry 中。XMLMapperBuilder 的 bindMapperForNamespace()方法 中,完成了映射配置文件与对应 Mapper 接口 的绑定。
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
// 获取映射配置文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
// 解析命名空间对应的类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// 是否已加载 boundType接口
if (!configuration.hasMapper(boundType)) {
// 追加个 "namespace:" 的前缀,并添加到 Configuration 的 loadedResources集合 中
configuration.addLoadedResource("namespace:" + namespace);
// 添加到 Configuration的mapperRegistry集合 中,另外,往这个方法栈的更深处看 会发现
// 其创建了 MapperAnnotationBuilder对象,并调用了该对象的 parse()方法 解析 Mapper接口
configuration.addMapper(boundType);
}
}
}
}
}
public class MapperRegistry {
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory(type));
// 解析 Mapper接口 type 中的信息
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
// 是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检查是否加载过该接口对应的映射文件,如果未加载,则创建 XMLMapperBuilder对象
// 解析对应的映射文件,该过程就是前面介绍的映射配置文件解析过程
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析 @CacheNamespace注解
parseCache();
// 解析 @CacheNamespaceRef注解
parseCacheRef();
// type接口 的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
// 解析 SelectKey、ResultMap 等注解,并创建 MappedStatement对象
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 如果解析过程出现 IncompleteElementException异常,可能是因为引用了
// 未解析的注解,这里将出现异常的方法记录下来,后面提供补偿机制,重新进行解析
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历 configuration 中的 incompleteMethods集合,集合中记录了未解析的方法
// 重新调用这些方法进行解析
parsePendingMethods();
}
}