手写 MyBatis

纯 MyBatis 开发步骤

  1. 读取 mybatis-config.xml 配置文件
  2. 构建 SqlSessionFactory
  3. 打开 SqlSession
  4. 获取 Mapper 接口对象
  5. 调用 Mapper 接口对象的方法操作数据库

源码流程

寻找入口 –> 断点跟踪 –> 先粗后细 –> 精略结合

目录结构

mapper
UUserInfoMapper 接口
1
2
3
4
5
6
7
8
9
10
// 接口定义执行数据库方法
List<MetaBasePO> selectOneLayer(
@Param("parentId") Integer parentId,
@Param("types") List<String> types,
@Param("queryWord") String queryWord,
@Param("deleted") Integer deleted,
@Param("status") Integer status,
@Param("containExtAttr") boolean containExtAttr
);

UUserInfoMapper.xml
1
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
43
44
45
46
47
<mapper namespace="com.mandalat.apolar.metadata.dao.mapper.MetaBaseMapper">
<resultMap id="BaseResultMap" type="com.mandalat.apolar.common.biz.model.metadata.po.MetaBasePO">
<result column="m_id" jdbcType="INTEGER" property="id" />
<result column="m_parent_id" jdbcType="INTEGER" property="parentId" />
<result column="m_group_id" jdbcType="INTEGER" property="groupId" />
<result column="m_code" jdbcType="VARCHAR" property="code" />
<result column="m_name" jdbcType="VARCHAR" property="name" />
<result column="m_type" jdbcType="VARCHAR" property="type" />
<result column="m_ext_attr" jdbcType="VARCHAR" property="extAttr" />
<result column="m_owner" jdbcType="VARCHAR" property="owner" />
<result column="m_post_to_db" jdbcType="TINYINT" property="postToDb" />
<result column="m_is_deleted" jdbcType="TINYINT" property="isDeleted" />
<result column="m_status" jdbcType="TINYINT" property="status" />
<result column="m_sort_no" jdbcType="INTEGER" property="sortNo" />
<result column="m_memo" jdbcType="VARCHAR" property="memo" />
<result column="m_ctime" jdbcType="TIMESTAMP" property="ctime" />
<result column="m_cuid" jdbcType="VARCHAR" property="cuid" />
<result column="m_mtime" jdbcType="TIMESTAMP" property="mtime" />
<result column="m_muid" jdbcType="VARCHAR" property="muid" />
</resultMap>

<select id="selectOneLayer" resultMap="BaseResultMap">
select m_id, m_parent_id, m_group_id, m_code, m_name, m_type, m_owner,
<if test="containExtAttr">
m_ext_attr,
</if>
m_post_to_db, m_is_deleted, m_status, m_sort_no, m_memo, m_ctime,
m_cuid, m_mtime, m_muid from md_meta_base where m_parent_id=#{parentId}
<if test="types != null and types.size()>0">
and m_type in
<foreach collection="types" item="type" open="(" close=")" separator=",">
#{type}
</foreach>
</if>
<if test="queryWord != null and queryWord != ''">
and (m_code like #{queryWord} or m_name like #{queryWord})
</if>
<if test="deleted != null">
and m_deleted = #{deleted}
</if>
<if test="status != null">
and m_status = #{status}
</if>
order by m_sort_no
</select>

</mapper>
model
  • 数据库对象类
mybatis
  • mapping
MyConfiguration
1
2
3
4
public class MyConfiguration (){
private MyEnviroment myEnviroment
private Map<String, MapperStatement> mapperStatementMap
}
MyEnviroment
1
2
3
4
5
6
7
// 映射 mybatis-config.xml
public class MyEnviroment (){
private String driver
private String url
private String username
private String password
}
MapperStatement
1
2
3
4
5
6
7
8
// 映射 Mapper.xml
public class MapperStatement (){
private String namesapce
private String id
private String parameterType
private String resultType
private String sql
}
  • executor
MyExecutor
1
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
43
44
45
46
// 数据库初始化操作
public class MyExecutor {
private DataSource datasource

public MyExecutor (MyConfiguration myconfig){
datasource = MyDataSource.getInstance(myconfig.getMyEnvironment())
}

public <T> T query (MapperStatement mapperStatement, Object parameter){
Connection connection = null;
PreparedStatement preparedState = null;
ResultSet res = null;
try{
connection = datasource.getConnection()
preparedState = connection.preparedStatement(mapperStatement.getSql())

if (parameter instanceof Integer) {
preparedState.setInt(1, (Integer) parameter)
}

res = preparedState.executeQuery()


} catch (SQLException e) {
e.printStackTrace();
} final {
if (null != res && null != preparedState) {
try {
res.close()
preparedState.close()
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) {
try {
datasource.release(connection)
} catch (SQLException e) {
e.printStackTrace();
}
}
}

return res
}
}
  • session
MySqlSession
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MySqlSession {
private MyConfiguration myconfig
private MyExecutor myexecutor

public MySqlSession (MyConfiguration myconfig, MyExecutor myexecutor){
this.myconfig = myconfig
this.myexecutor = myexecutor
}

public <T> T getMapper(Class<T> clazz){
// 动态代理对象
MaperProxy maperProxy = new MaperProxy(this)

// ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
return (T)Proxy.newProxyInstance(clazz.getClass().getClassLoader(),
Class<?>[] {clazz},
InvocationHandler h)
}

public <T> T doSomthing(int args, String statementKey){
return myexecutor.query(myconfig.getMapperStatementMap().get(statementKey), args)
}
}
MySqlSessionFactory
1
2
3
4
5
6
7
8
9
10
11
12
public class MySqlSessionFactory {
private MyConfiguration myconfig

public MySqlSessionFactory(MyConfiguration myconfig){
this.myconfig = myconfig
}

public MySqlSession openSession(){
MyExecutor myExecutor = new MyExecutor(myconfig)
return new MySqlSession(myconfig, myExecutor)
}
}
MySqlSessionFactoryBuild
1
2
3
4
5
public MySqlSessionFactory build(InputStream inputStream){
// 解析 XML
MyConfiguration myconfig = new XMLConfigBuilder(inputStream).parse()
return new MySqlSessionFactory(MyConfiguration myconfig)
}
  • proxy
MaperProxy
1
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
public class MaperProxy implements InvocationHandler (){
private MySqlSession mySqlSession

public MaperProxy(MySqlSession mySqlSession){
this.mySqlSession = mySqlSession
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 实现查询方法
Class<?> clazz = method.getReturnType()
// 返回类型是集合
if (Collection.class.isAssignableForm(clazz)) {
return mySqlSession.doSomthing(args)
}
// 返回类型是Map
else if (Map.class.isAssignableForm(clazz)) {
return mySqlSession.doSomthing(args)
}
// 返回对象
else {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName()
return mySqlSession.doSomthing(statementKey, args)
}
return null
}
}
  • parsing
XMLConfigBuilder
解析 XML 获取环境对象,Mapper 对象
  • pool
MyDataSourceInterface
1
2
3
public interface MyDataSourceInterface extends DataSource{
// 接口可以实现默认方法
}
MyDataSource
1
2
3
4
public class MyDataSource implements MyDataSourceInterface{
// 实现部分方法即可
// 数据库连接池
}
pom.xml
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java<artifactId>
</dependency>
</dependencies>
main
1
2
3
4
5
6
7
8
9
10
// Resources <==> 类名.class.getClassLoder()
public class Application {
public static void main(String[] args) {
InputStream inputStream = Resources.getResourceAsStream('mybatis-config.xml')
MySqlSessionFactory sqlsfactory = new MySqlSessionFactoryBuild().build(inputStream)
MySqlSession session = sqlsfactory.openSession()
UUserInfoMapper uuserInfoMapper = session.getMapper(UUserInfoMapper.class)
UUserInfo uuserInfo = uuserInfoMapper.selectByPrimaryKey(1)
}
}
mybatis-config.xml
1
2
environments -> dataSource
mappers

pom.xml 说明

  1. 全局配置 project build
配置示例
1
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<build>
// 执行build任务时,没有指定目标,将使用默认值。当前配置相当于执行 mvn install
<defaultGoal>install</defaultGoal>

// build 目标文件的存放位置
<directory>${basedir}/target</directory>

// build 目标文件名称
<finalName>${artifactId}-${version}</finalName>

<filters>
// 将属性值应用到支持 filter 的 resources 中
// maven 的默认 filter 文件夹为 ${basedir}/src/main/filters
<filter>filters/filter1.properties</filter>
</filters>

// 用于包含或者排除某些资源文件,描述与项目关联的文件是什么和在哪里
<resources>
<resource>
// 指定 build 后的 resource 存放的文件夹,默认 basedir
<targetPath>META-INF/plexus</targetPath>

// filter 是否激活
<filtering>false</filtering>

// 定义 resource 文件所在文件夹
<directory>${basedir}/src/main/plexus</directory>

// 指定哪些文件将被匹配,以*作为通配符
<includes>
<include>configuration.xml</include>
</includes>

// 指定哪些文件将被忽略
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>

// 指定使用的插件
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.0</version>

<extensions>false</extensions>
<inherited>true</inherited>

// 配置 plugin 期望得到的 properties
<configuration>
<classifier>test</classifier>
</configuration>

// 作为 plugin 的依赖
<dependencies>...</dependencies>
<executions>...</executions>
</plugin>
</plugins>
</build>
pluginManagement <plugins> '子 pom' 直接使用 '父 pom' 定义的 plugin
  1. 配置 profile build
1
2
3
4
5
<profiles>  
<profile>
<build></build>
</profile>
</profiles>