Mybatis源码系列0-从JDBC到Mybatis
缘起JDBC如何使用痛点分析轮子Mybatis(脱离Spring)使用初级阶段:DefaultSqlSession(脱离Spring)使用中级阶段:Mapper(脱离Spring)使用高级阶段:SqlsessionManagerSpring-JDBC总结
缘起JDBC
如何使用
说Mybatis之前得先讲讲JDBC
public class JDBCTest {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://XXXXX/test";
String user = "XXXX";
String password = "XXXX";
try {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
// 3.获取statement,preparedStatement
String sql = "select * from user where id=?";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
// 设置参数
prepareStatement.setLong(1, 1l);
// 4.执行查询,获取结果,
ResultSet rs = prepareStatement.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("userName"));
System.out.println(rs.getString("name"));
System.out.println(rs.getInt("age"));
}
} finally {
// 5.关闭连接,释放资源
if (rs != null) {
rs.close();
}
if (prepareStatement != null) {
prepareStatement.close();
}
if (connection != null) {
connection.close();
}
}
}
}
痛点分析
如果我们在一个项目中所有与数据库打交道的地方都写一堆这样这个东西,肯定是不友好的。
我们分析存在的问题:
- 硬编码东西,最好统一管理。我们看到第一步,二步中的驱动,数据库url,用户名,密码都是在项目启动后不变的。所以,我们应该搞一个属性配置,专门存放这些不变的东西。
- 获取连接: 第二步存在问题就是,我们不能在每次获取数据链接的地方,都把
DriverManager.getConnection(url, user, password);
写一遍吧。好的写法是对其封装一个类工厂,只在工厂类中写一遍,直接从工厂类中获取Connection - 第三步:其实有两个重点:(1)sql 的分散JAVA代码中(2)sql 的参数拼接问题
- 第四步:就是结果的映射问题,我们通常都会定义JAVABean映射数据库字段,这种结果的处理,其实就是体力活,我们应该做一个处理器专门处理这种映射问题
- 第五步:链接的关闭也是一个力气活,并且如果一不小心忘了,可就不好了。所以应该做一个保证能执行完关闭的,让程序不要总是想着我关闭连接了吗?
针对这5个步骤的问题,我们自己也可以写出几个工具类出来,其实就是在造轮子了。
Mybatis 就是这么一个框架,帮助我们解决JDBC在使用上的不方便。
轮子Mybatis
(脱离Spring)使用初级阶段:DefaultSqlSession
- 配置
对于硬编码的信息,将其维护到XML中,进行集中配置管理,。
mybatis-config.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>
<!-- 1.环境配置,可以配置多个,default:指定采用哪个环境 -->
<environments default="test">
<!-- id:标识环境-->
<environment id="test">
<!-- 事务管理器,JDBC类型的事务管理器 -->
<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" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 2.sql模板文件的位置的配置 -->
<mappers>
<mapper resource="mappers/UserMapper.xml" />
</mappers>
</configuration>
UserMapper.xml: SQL也维护到XML中,使其从JAVA代码中脱离出来,进行集中管理。
<?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:命名空间,命名空间唯一 -->
<mapper namespace="UserMapper">
<!--
id:SQL语句的唯一标识,同命名空间下唯一。
resultType:sql语句查询结果集的封装类型
-->
<select id="selectUser" resultType="com.wqd.model.User">
select * from user where id= #{id}
</select>
</mapper>
- 编码
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//根据配置文件,构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
//执行SQL,获取结果集
//(SQL通过命名空间+SQLID 的格式定位)
User user = sqlSession.selectOne("MyMapper.selectUser", 1);
}finally {
sqlSession .close();
}
相对于JDBC:
- 配置集中管理,配置在xml中
- SQL集中管理,管理在Mapper.xml中,代码中通过namespace+id的形式进行SQL的定位
- 把连接抽象成session会话。 session的获取,统一由工厂提供,session 也做到了统一管理。
- 参数解析,不需要了,我们只管传递参数。
- 结果映射不用管,只管接收就完了。
将大量的编码工作,改为了配置工作。
仍然存在的可优化的点:
- SQL的定位仍然需要字符串的形式进行定位,不够优雅。
- session 的关闭仍然要自己关闭。
(脱离Spring)使用中级阶段:Mapper
为了解决SQL定位的问题,Mybatis还提供了Mapper功能。
通常,我们习惯用一个Dao来表示对数据库的访问操作。
package com.wqd.mapper
public interface UserMapper {
public User selectUser();
}
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">
<!-- namespace 定义为接口的 全限定名 -->
<mapper namespace="com.wqd.mapper.UserMapper">
<!--
id:定义为方法名。
resultType:sql语句查询结果集的封装类型
-->
<select id="selectUser" resultType="com.wqd.model.User">
select * from user where id= #{id}
</select>
</mapper>
编码使用
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//根据配置文件,构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
//获取Mapper ,执行操作
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUser(1);
}finally{
sqlSession.close();
}
可以看出SQL的定位,通过方法调用的方式来解决了。 这样是比较符合开发人员的开发习惯的。
可优化点:
- session需要自己关闭。
(脱离Spring)使用高级阶段:SqlsessionManager
对于session的关闭,往往是开发人员经常遗忘的东西。有时候程序是比人靠谱的。在一些不要控制session.commit的场合,能自动关闭最好。
解决这个问题,Mybitis还提供了一个类SqlsessionManager。
从上图我们可以看出,SqlsessionManager是Sqlsession的实现类,也就是SqlsessionManager与DefaultSqlSession 是平级的,两个都可以表示会话。
但是
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//根据配置文件,构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sqlSessionFactory);
UserDao userMapper =sqlSessionManager.getMapper(UserDao.class);
我们看到SqlsessionManager 不需要我们手动关闭session了。是不是又轻松了许多?
其实,SqlsessionManager通过AOP技术,在执行逻辑后进行了增强,使用的开发人员不必关心connection/session的关闭问题。
当然SqlsessionManager 还解决了DefaultSqlSeesion线程不安全问题。具体SqlsessionManager 是如何实现的,在后续文章中再说。
Spring-JDBC
大框架spring,也我们准备了一个JDBC的轮子Spring-JDBC
Spring-JDBC其实就是我们常说的:JDBCTemplate
不同于mybatis的是
- mybatis具有更加高级的操作。mybatis是一个单独的框架存在
- JDBCTemplate 是spring的一个模块,相对功能上比较简单
- JDBCTemplate 是可以被spring容器进行管理的。
总结
Mybatis 通过对JDBC的封装,使我们可以更加灵活的操作数据库。
接下来,我将通过源码去探索Mybatis内部一些优秀的设计,来解开Mybatis设计的秘密。