种一棵树最好的时间是十年前,其次是现在;
一个Java开发的渣渣,工作两年回头才发现自己什么都是会一点;
总结总结不做,说话说话不会。从现在开始从基础开始扎实学习。学习之后一定要求自己总结复盘,这样才有成长。
ORM 简介
面向对象程序设计(OOP - Object Oriented Programming)是企业级开发常用的设计方式。
我们常用的语言,如Java、.Net、C++ 等,是面向对象的编程语言。
数据库产品,如MySql、Oracle等,则都是关系型数据库。
在系统开发的过程中,则需要使用面向对象的思维实现业务逻辑,但设计数据库表或操作数据库记录时,则需要通过关系型的思维方式考虑问题。所以在应用程序与关系型数据库之间进行交互时,数据在对象和关系结构中的表、列、字段等之间进行转换。
JDBC(Java Database Connectivity) 是 Java 与数据库交互的统一 API,实际上分为两组 API,一组是面向应用程序开发人员的API,是一个标准的Java API 独立于各个厂家的数据库实现。另一组就是面向数据库驱动程序开发人员的API,是用于编写数据库驱动为应用程序开发提供支持。
在实际开发Java 系统时,我们可以通过JDBC 完成多种数据库操作。这里以传统 JDBC 编程中的查询操作为例进行说明,其主要步骤如下:
(1)注册数据库驱动类,明确指定数据库URL 地址、数据库用户名、密码等连接信息。
(2)通过 DriverManager 打开数据库连接。
(3)通过数据库连接创建 Statement 对象。
(4)通过 Statement 对象执行SQL 语句,得到ResultSet 对象。
(5)通过 ResultSet 读取数据,并将数据转换成JavaBean 对象。
(6)关闭 ResultSet 、Statement 对象以及数据库连接,释放相关资源。
public class JdbcHelloWord {
public static void main(String[] args) throws Exception {
//定义一个MYSQL链接对象
Connection con = null;
// MYSQL驱动
Class.forName("com.mysql.jdbc.Driver").newInstance();
// 注册数据库驱动类,明确指定数据库URL 地址、数据库用户名、密码等连接信息。
// 通过 DriverManager 打开数据库连接。
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_self?useUnicode=true", "root", "1234");
// 更新一条数据
String updateSql = "UPDATE user SET age = '25' WHERE user_id = ?";
// 通过数据库连接创建 Statement 对象。
PreparedStatement pstmt = con.prepareStatement(updateSql);
pstmt.setString(1, "1");
// 通过 Statement 对象执行SQL 语句,得到ResultSet 对象。
long updateRes = pstmt.executeUpdate();
System.out.print("UPDATE:" + updateRes);
// 查询数据并输出
String sql = "select user_id, username, age from user where user_id = ?";
PreparedStatement pstmt2 = con.prepareStatement(sql);
pstmt2.setString(1, "1");
// 通过 Statement 对象执行SQL 语句,得到ResultSet 对象。
ResultSet rs = pstmt2.executeQuery();
// 通过 ResultSet 读取数据,并将数据转换成JavaBean 对象。
while (rs.next()) { //循环输出结果集
String userId = rs.getString("userId");
String username = rs.getString("username");
Integer age = rs.getInt("age");
System.out.print("\r\n\r\n");
System.out.print("lfPartyId:" + lfPartyId + "partyName:" + partyName + "age: " + age);
}
}
}
其中 步骤1~步骤4 与 步骤 6 在查询语句中都会出现,在增改删语句中也会出现类似的代码,对此可以封装成一个工具类。
步骤 5 是完成关系模型到对象模型的转换,如果使用通用的方式封装是比较困难的。
为了解决该问题,ORM(Object Relational Mapping,对象-关系映射)框架应运而生。如图所示,ORM框架的主要功能就是根据映射配置文件,完成在对象模型与关系模型之间的映射(步骤5),同时也屏蔽了上述重复的代码(步骤1~步骤4 与 步骤 6 ),只暴露简单的 API 供开发人员使用。
实际生产环境中对系统的性能是有一定要求的,数据库作为系统中比较珍贵的资源,极易成为整个系统的性能瓶颈,所以我们不能像上述JDBC 操作那样简单粗暴地直接访问数据库、直接关闭数据库连接。应用程序一般需要通过集成缓存、数据源、数据库连接池等组件进行优化,如果没有ORM 框架的存在,就要求开发人员熟悉相关组件的API 井手动编写集成相关的代码,这就提高了开发难度并延长了开发周期。
持久层框架
- Hibernate
- JPA(Java Persistence API)
- Spring JDBC
- MyBatis
MyBatis
与其他持久层框架一样
1、可以屏蔽底层重复性的原生 JDBC 代码。
2、通过配置文件或注解将ResultSet 映射为 Java对象,其映射规则可以嵌套其他映射规则以及子查询,从而实现复杂的映射逻辑,可以实现一对一,一对多,多对多映射与双线映射。
优点:
相较于Hibernate, MyBatis 更加轻量级,可控性也更高,在使用MyBatis 时我们直接在映射配置文件中编写待执行的原生SQL 语句,这就给了我们直接优化SQL 语句的机会,让SQL语句选择合适的索引,能更好地提高系统的性能,比较适合大数据量、高并发等场景。在编写SQL 语句时,我们也可以比较方便地指定查询返回的列,而不是查询所有列并映射对象后返回,这在列比较多的时候也能起到一定的优化效果。
对同一数据集的查询条件可能是动态变化的,如果读者有使用 JDBC 或其他类似框架的经历就能体会到,根据不同条件拼接SQL 语句是一件非常麻烦的事情,尤其是拼接过程中要确保在合适的位置添加“ where ”、“ and ”、“ in ”等SQL 语句的关键字以及空格、逗号、等号等分隔符,而且这个拼接过程非常枯燥、没有技术含量,可能经过反复调试才能得到一个可执行的SQL 语句。MyBatis 提供了强大的动态SQL 功能来帮助开发人员摆脱这种窘境,开发人员只需要在映射配置文件中编写好动态SQL 语句, MyBatis 就可以根据执行时传入的实际参数值拼凑出完整的、可执行的SQL 语句。
比较:
从性能角度来看:Hibernate 生成的SQL 语句难以优化, Spring JDBC 和 MyBatis 直接使用原生SQL 语句,优化空间比较大,MyBatis 和 Hibernate 有设计良好的缓存机制, 三者都可以与第三方数据源配合使用:
从可移植性角度来看:Hibernate 帮助开发人员屏蔽了底层数据库方言,而Spring JDBC 和MyBatis 在该方面没有做很好的支持,但实践中很少有项目会来回切换底层使用的数据库产品,所以这点并不是特别重要;
从开发效率的角度来看:Hibernate 和 MyBatis 都提供了XML 映射配置文件和注解两种方式实现映射, Spring JDBC 则是通过ORM 化的Callback 的方式进行映射。读者在进行技术选型时,可以从更多角度进行比较,权衡性能、可扩展性、开发人员技术枝等多个方面选择合适的框架。
MyBatis 整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层、核心处理层和接口层,如图:
基础支持层
- 反射模块
Java 中的反射虽然功能强大,但对大多数开发人员来说,写出高质量的反射代码还是有一定难度的。MyBatis 中专门提供了反射模块,该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。
- 类型转换模块
MyBatis 为简化配置文件提供了别名机制, 该机制是类型转换模块的主要功能之一。类型转换模块的另一个功能是实现JDBC 类型与Java 类型之间的转换,该功能在为SQL 语句绑定实参以及映射查询结果集时都会涉及。在为SQL 语句绑定实参时, 会将数据由Java 类型转换成JDBC 类型;而在映射结果集时,会将数据由JDBC 类型转换成 Java 类型。
- 日志模块
无论在开发测试环境中,还是在线上生产环境中,日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位Bug 代码,也可以帮助运维人员快速定位性能瓶颈、等问题。目前的Java 世界中存在很多优秀的日志框架,例如Log4j 、Log4j2, slf4j 等。MyBatis 作为一个设计优良的框架,除了提供详细的日志输出信息,还要能够集成多种日志框架,其日志模块的一个主要功能就是集成第三方日志框架。
- 资源加载模块
资源加载模块主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。
- 解析器模块
解析器模块的主要提供了两个功能: 一个功能是对XPath 进行封装,为MyBatis 初始化时解析mybatis-config.xml 配置文件以及映射配置文件提供支持;另一个功能是为处理动态SQL 语句中的占位符提供支持。
- 数据源模块
数据源是实际开发中常用的组件之一。现在开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升ORM 框架乃至整个应用的性能都是非常重要的。MyBatis 自身提供了相应的数据源实现,当然MyBatis 也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中。
- 事务管理
MyBatis 对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单实现。在很多场景中, MyBatis 会与Spring 框架集成,并由Spring 框架管理事务;
- 缓存模块
在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少相当一部分数据库的压力。
MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要读者注意的是, MyBatis 中自带的这两级缓存与MyBatis 以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用Redis 、Memcache 等缓存产品。
- Binding 模块
通过前面的示例我们知道,在调用SqI Session 相应方法执行数据库操作时,需要指定映射文件中定义的SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。为了尽早发现这种错误, MyBatis 通过Binding 模块将用户自定义的Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义Mapper 接口中的方法执行相应的SQL 语句完成数据库操作,从而避免上述问题。
核心处理层
在核心处理层中实现了MyBatis 的核心处理流程,其中包括 MyBatis 的初始化以及完成一次数据库操作的涉及的全部流程。
- 配置解析
在MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。例如,示例中定义的<resultMap>节点(即ResultSet 的映射规则)会被解析成 ResultMap 对象:示例中定义的<result> 节点(即属性映射)会被解析成 ResultMapping 对象。之后利用该Configuration 对象创建SqlSessionFactory 对象。待My Batis 初始化之后,开发人员可以通过初始化得到SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。
- SQL解析与scripting 模块
拼凑 SQL 语句是一件烦琐且易出错的过程,为了将开发人员从这项枯燥无趣的工作中解脱出来, MyBatis 实现动态SQL 语句的功能,提供了多种动态SQL 语句对应的节点,例如,<where>节点、<if>节点、<foreach>节点等。通过这些节点的组合使用, 开发人员可以写出几乎满足所有需求的动态SQL 语句。
MyBatis 中的 scripting 模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。之后会处理SQL 语句中的占位符,绑定用户传入的实参。
- SQL 执行
SQL 语句的执行涉及多个组件,其中比较重要的是 Executor 、StatementHandler 、ParameterHandler 和 ResultSetHandler 。
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler 完成。StatementHandler 首先通过 ParameterHandler 完成SQL语句的实参绑定,然后通过 java.sql.Statement 对象执行SQL 语句并得到结果集,最后通过ResultSetHandler 完成结果集的映射,得到结果对象并返回。
- 插件
插件是MyBatis 提供的扩展机制之一,用户可以通过添加自定义插件在SQL 语句执行过程中的某一点进行拦截。
Mybatis 自身的功能虽然强大,但是并不能完美切合所有的应用场景,因此MyBatis提供了插件接口,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。用户自定义插件也可以改变Mybatis 的默认行为,例如,我们可以拦截SQL 语句并对其进行重写。由于用户自定义插件会影响My Batis 的核心行为,在使用自定义插件之前,开发人员需要了解 MyBatis 内部的原理,这样才能编写出安全、高效的插件。
接口层
接口层相对简单,其核心是SqlSession 接口,该接口中定义了MyBatis 暴露给应用程序调用的API ,也就是上层应用与MyBatis 交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。
MyBatis 实例
使用 Mybatis 查询数据库如下:
(1)配置 MyBatis 初始化配置文件(Configuration.xml)与对应的 mapper(UserMapper.xml)文件;
(2)Mapper 对应的 IUserMapper.Java,提供给Java应用调用绑定Mapper.xml 的接口;
(3)使用;
Configuration.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test_self?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!--使用package自动搜索的模式,这样指定package下所有接口都会被注册为mapper-->
<!--<mappers>
<package name="org.mybatis.internal.example"/>
</mappers>-->
<mappers>
<mapper resource="org/mybatis/internal/example/UserMapper.xml"/>
<!--<mapper url="file:///org/mybatis/internal/example/UserMapper.xml"/>
<mapper class="org.mybatis.internal.example.mapper.UserMapper"/>-->
</mappers>
</configuration>
IUserMapper.java
public interface UserMapper {
User getUser(Integer userId);
Map findName();
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.internal.example.mapper.UserMapper">
<select id="getUser" parameterType="int" resultType="org.mybatis.internal.example.pojo.User">
select user_id userId, username username, age age from self_user where user_id = #{id}
</select>
<select id="findName" statementType="CALLABLE" resultType="java.util.Map">
{call findName(
#{name, mode=IN},
#{email_address, mode=OUT, jdbcType=VARCHAR}
)}
</select>
</mapper>
MybatisHelloWorld.java
public class MybatisHelloWorld {
public static void main(String[] args) {
String resource = "org/mybatis/internal/example/Configuration.xml";
Reader reader;
try {
// mybatis 初始化
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 执行 sql 语句
// 首先要拿到代表JDBC底层连接的一个对象,这在mybatis中的实现就是SqlSession
// mybatis的事务管理模式分为两种,自动提交和手工提交,DefaultSqlSessionFactory的openSession中重载中,
// 提供了一个参数用于控制是否自动提交事务, 该参数最终被传递给 java.sql.Connection.setAutoCommit()方法
// 用于控制是否自动提交事务(默认情况下,连接是自动提交的)
try (SqlSession session = sqlMapper.openSession(true)) {
/*
* 简单来说,SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接(当然其背后可能是从jdbc连接池获取);
* session中的各种selectXXX方法或者调用mapper的具体方法就是集合了JDBC调用的第3、4、5、6步。
*
* 根据sql语句使用xml进行维护或者在注解上配置,sql语句执行的入口分为两种:
* 第一种,调用 org.apache.ibatis.session.SqlSession 的 crud 方法比如 selectList/selectOne 传递完整的语句id直接执行;
* 第二种,先调用SqlSession的getMapper()方法得到mapper接口的一个实现,然后调用具体的方法。
* 除非早期,现在实际开发中,我们一般采用这种方式。
*/
User user = session.selectOne("org.mybatis.internal.example.mapper.UserMapper.getUser", 1);
/*
* 通过SqlSession.getMapper执行CRUD语句
*/
// UserMapper mapper = session.getMapper(UserMapper.class);
// // 这样当我们应用层执行List users = mapper.getUser(1);的时候,JVM会首先调用 MapperProxy.invoke,如下:
// User user = mapper.getUser(1);
System.out.println(user.getUserId() + "," + user.getUsername() + "," + user.getAge());
// session.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
使用 MyBatis 操作数据就可以使用了。
参考:《MyBatis 技术内幕》
以上是学习MyBatis 技术内幕时的总结,有关于Mybatis的相关总结会继续下去(立flag)如果有误欢迎留言指出。