前言:工作这么长时间,从刚毕业的SSM就开始接触mybatis,再到后来的springboot,然后到现在的springcloud。mybatis一直做为和数据库交互的框架。最近发现,虽然mybatis一直在用,但是好像也仅仅是用,原理什么的好像也不太清楚,决定从头开始,了解mybatis到底做了什么。
传统JDBC同数据库的交互方式:简单的代码,这个时代还是JSP+Servlet+Mysql+JDBC的时代
// 这里是一个User对象
User user = this.getUser();
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriveManager.getConnection(url,username,password);
// 用?防止SQL注入
String sql = "insert into user (id,name,age) values (?,?,?)";
PreparedStatement prepare = conn.prepareStatement(sql);
// 为上面的三个参数赋值
prepare.setString(1,user.getId);
prepare.setString(2,user.getName);
prepare.setInt(3,user.getAge);
// 第一种情况:执行SQL,获得受影响的行数
int rows = prepare.executeUpdate();
// 第二种情况:执行查询SQL,获取结果集
string querySql = "select * from user";
Result result = prepaer.executeQuery();
while(res.next()){
// 对结果集的操作
res.getString(1);
res.getString(2);
}
// 关闭
res.close();
prepare.close();
conn.close
1.加载驱动
2.获取数据库连接
3.书写SQL、并对SQL中的参数进行赋值
4.获取结果集
5.关闭连接
Spring出现、Mybatis出现,二者相结合。
其实Spring也好,Mybatis也好,官方介绍都是基本从XML文件开始给出的示例,即使到现在XML使用少了,但是整体的结构是不变的,这个后面会说到。个人建议,先从XML配置文件入门学习,在现如今springboot、springcloud横行的时代,虽然大家都能在框架里写东西,但是一旦配置出了问题,或者自己动手配置的时候基本只能照着已有的项目进行粘贴、复制。所有还是要至少了解一下各个框架的基本原理,死记硬背是永远不行的。
Mybatis基本配置如下:文件名字为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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/dhb?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="12345"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--单个mapper文件的配置-->
<mapper resource="mapper/UserMapper.xml"/>
<!--整个dao包下面的配置-->
<!--<package name="com.example.mybatis.demomybatis.dao"/>-->
</mappers>
</configuration>
可以看出Mybatis的顶级为configuration元素,接下来是environment元素,里面配置了事务类型、数据源、在数据源里配置数据库驱动、url、username、password。接下来是一个一个的mapper文件,resource配置的是每个mapper.xml的路径,也可以批量配置。
UserMapper.java接口如下:
public interface UserMapper {
void addUser(UserEntity entity);
}
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="com.example.mybatis.demomybatis.dao.UserMapper">
<insert id="addUser" parameterType="com.example.mybatis.demomybatis.entity.UserEntity">
INSERT INTO user (id, name, age) values (#{id},#{name},#{age})
</insert>
</mapper>
这里先不使用spring和mybatis结合的方式进行,先把mybatis怎么运行的搞懂再和spring结合。
这里直接使用官方文档给出的示例,做一个简单的插入操作:
@SpringBootTest
public class MybatisTests {
@Test
public void testConfiguration() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 读取配置文件,将所有配置好的Mapper接口,在XMLConfigBuilder中parseConfiguration方法里的mapperElement子方法里加进去,
// 最终的执行方法在XMLMapperBuilder.parse()里的bindMapperForNamespace()调用,
// Configuration.addMapper()方法里通过MapperRegistry.addMapper()方法,
// 放到一个Map里面去,
// 该Map的Key是Mapper接口的完整路径(仅仅是接口路径而不是接口下面具体方法的路径,要注意,接口方法的具体路径会在下面动态代理那里拿到),
// 该Map的value是构造一个新的MapperProxyFactory,
// MapperProxyFactory以便于后面调用sqlSession.getMapper(Mapper接口.class)可以通过该MapperProxyFactory生成当前Mapper接口的动态代理类MapperProxy,
// 具体可参考MapperRegistry.getMapper()方法即可,里面就是一个new了一个Mapper接口的动态代理对象 MapperProxy
SqlSessionFactory factory = builder.build(inputStream);
SqlSession sqlSession = factory.openSession();
UserEntity entity = new UserEntity();
entity.setAge(77);
entity.setName("op7");
entity.setId(7);
// 第一种方式、获取对应的mapper,然后调用对应的方法即可,这里获取的UserMapper 的动态代理类MapperProxy对象
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 执行的时候调用MapperProxy.invoke方法执行,里面最后会执行 mapperMethod.execute(sqlSession, args),
// 然后MapperMethod.execute(),根据不同的SQLCommand(insert,update,delete,select)执行sqlSession.insert等不同的方法,
// 接下来的执行就和第二种方式一毛一样了
// mapper.addUser(entity);
// 第二种方式、直接调用sqlsession的内置方法,两个参数,第一个参数是,mapper里的具体方法的完整路径,第二个参数是方法的入参
sqlSession.insert("com.example.mybatis.demomybatis.dao.UserMapper.addUser",entity);
sqlSession.commit();
sqlSession.close();
}
}
可以看出核心insert是有SqlSession对象完成的。SqlSession是有SqlSessionFactory.openSessoin()开启的,SqlSessionFactory又是由SqlSessionFactoryBuilder.build(读取的配置文件)生成的。
这里写了两种方式完成了insert操作,先以官方的方式、第二种方式分析。
sqlSession.insert("Mapper.方法","参数"),debug发现,接下来执行
参数就是我们第一步传的参数,第一个参数为Mapper里的方法的完整路径,第二个参数为该方法的参数,一个对象
首先会在全局configuration中拿到MapperStatement对象,这个至关重要,至于什么时候把MapperStatemnt放到configuration中的,会面会细说。包括MapperStatemnt中的sqlCommandType和statementType都会详细解释,本篇不做讨论。
接下来会执行DefaultSession里的update方法,调用executor.update,注意执行到这里,已经知道里SQL命令类型为insert,Statement为PrepareStatement类型,至于怎么获得的,以后会说,这里只说SQL的执行整体流程。
接下来执行BaseExecutor里的update方法,最终会由BaseExecutor的其中一个子类SimpleExecutor执行update方法
这里的关键两行代码是 stmt = prepareStatement()这个方法,和handler.update(),基本所有的最终的操作是在这两步完成的,debug进去看看到底干了什么
看到这个方法里的方法名字就知道了,获取连接和设置事务,再结合传统的JDBC,是不是清晰了一点
最后一步进入PrepareStatementHandler(是BaseStatementHandler的子类),这里的ps.execute()是不是特别熟悉。
到此为止整个SQL的前半部分就是这样的,返回的结果集后面研究。
接下来研究前半段SQL究竟怎么执行的,Myabtis是如何将我们写的SQL和参数揉到一起,并且请求数据库进行操作的?第二篇分析