# MyBatis使用与实现原理初步简介
# mybatis是什么、为什么要用mybatis呢
mybatis是一种orm框架,是常见的DAO层的工具框架,通过mybatis能够和数据库进行交互,写入数据查询数据等。 通过mybatis能够避免直接使用jdbc时繁琐的数据类型转换、连接管理等工作,通过简单的xml或注解就能与数据库进行交互。
与mybatis类似的框架还有spring jdbc, hibernete。
# mybatis怎么用
我们以一个从数据库中查询一个user表的记录为例,看一下mybatis的使用方法
# 原生mybatis用法
直接使用mybatis(相比使用spring集成方式)
引入依赖, 版本号3.5.7可以换成最新版本
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
2
3
4
5
在resources下创建mybatis配置文件
<?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"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.github.liuzhengyang.mybatis.plain.mapper"/>
</mappers>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
创建User对象,这是我们和数据库中的记录映射的model类。
public class User {
private long id;
private String name;
public long getId() {
return id;
}
public User setId(long id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public User setName(String name) {
this.name = name;
return this;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("User{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
创建一个UserMapper类,这是在程序中调用sql操作的接口类
public interface UserMapper {
User getUserById(long id);
}
2
3
创建一个UserMapper.xml文件,这是与UserMapper.java对应的具体的sql语句和配置文件,当然sql也可以用注解的方式在UesrMapper.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="com.github.liuzhengyang.mybatis.plain.mapper.UserMapper">
<select id="getUserById" resultType="com.github.liuzhengyang.mybatis.plain.model.User">
select * from user where id = #{id}
</select>
</mapper>
2
3
4
5
6
7
8
9
最后开始使用
// mybatis配置文件相对于resources文件夹的位置
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory,这个对象应该全局保存
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建SqlSession,每次请求(线程)创建一个SqlSession,因为sqlsession不是线程安全的
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过SqlSession拿到UserMapper对象
UserMapper mapper = session.getMapper(UserMapper.class);
// 调用UserMapper
User user = mapper.getUserById(1);
System.out.println(user);
}
2
3
4
5
6
7
8
9
10
11
12
13
# mybatis springboot用法
在和Spring使用时,mybatis spring能够帮助我们自动给Mapper注册(需要有@Mapper注解)到spring的beanfactory中,这样我们就可以通过@Autowired/@Resource引用到Mapper而不用重复操作SqlSessionFactory了
添加mybatis spring依赖
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<!--此处的配置是识别到mapper.xml文件,也可以在application.properties中配置-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.* </include>
</includes>
</resource>
</resources>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
不再需要mybatis-config.xml,通过application.properties配置好数据源,mybatis spring boot starter会自动帮助我们进行其他配置
application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/user
spring.datasource.username=root
spring.datasource.password=
2
3
4
给UserMapper加上@Mapper注解
@Mapper
public interface UserMapper {
User getUserById(long id);
}
2
3
4
然后就可以通过Autowired引入UserMapper使用了,而不用操作SqlSessionFactory
@SpringBootApplication
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Autowired
private UserMapper userMapper;
@RequestMapping("/user/{id}")
public User user(@PathVariable("id") long id) {
return userMapper.getUserById(id);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mybatis实现原理
看完了mybatis的使用方式再来看一下mybatis的实现原理。
mybatis中通过UserMapper这样的Mapper类操作,就可以最终完成数据库操作。
UserMapper是一个接口,mybatis帮我们自动创建了接口的实现(代理类,可以参考我之前的设计和分析Java动态代理的技术实现 (opens new window)),spring data也是类似的实现和使用方法(不过spring data会更简单,很多默认的增删改查操作都有默认实现)。
我们就从userMapper.getUserById是如何执行的来分析mybatis的工作流程。
创建SqlSessionFactoryBuilder
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2
3
SqlSessionFactoryBuilder会读取解析xml配置文件,然后会构建Mapper Class到MapperProxyFactory的映射。 MapperProxyFactory负责在调用getMapper时返回MapperProxy, MapperProxy就是Mapper接口的实现代理。 在Proxy执行时,会找到方法对应的sql,完成类型转换、执行sql语句等操作。
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
...
}
public class MapperMethod {
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
....
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32