手写Mybatis框架

第一步

创建maven项目,引入相关依赖,打包方式改为jar

1
2
3
4
5
6
7
8
9
10
11
12
13
<packaging>jar</packaging>    
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
第二步

创建Resources工具类,获得核心配置文件。再创建SqlSessionFactoryBuilder类和SqlSessionFactory类

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
package org.god.ibatis.utils;

import java.io.InputStream;

/**
* 工具类,用于获取一个InputStream输入流
* 从类路径下加载资源
*/
public class Resources {
//工具类的方法一般都是静态的,也就是说不用实例化对象就能调用方法
//为了避免new对象,将工具类私有化,这是一种习惯
private Resources(){}

/**
*
* @param resource 类路径下的资源
* @return 返回一个输入流
*/
public static InputStream getResource(String resource){
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
return resourceAsStream;
}

}

第三步

设计SqlSessionFactoryBuilder和SqlSessionFactory类。

  1. 通过build方法创建SqlSessionFactory对象,所以SqlSessionFactoryBuilder中有一个build方法。
  2. build方法中肯定有参数,这些参数需要用来初始化SqlSessionFactory对象
  3. 思考SqlSessionFactory中会有哪些属性。通过观察核心配置文件可知(一个environment对应一个SqlSessionFactory对象),SqlSessionFactory中应该有事务管理属性、数据源属性以及sqlMapper属性。其中前两个属性都可以封装为一个对象,最后一个属性其实对应的就是mapper标签(用于关联sqlMapper.xml文件的)。mapper标签可能有多个,而且mapper.xml文件中的sql语句也会有多条,用对象来维护不可行。解决方案:使用map集合来维护,key值存放sqlId,value存放一个sqlMapperStatement对象(封装了sql语句以及其他属性)
  4. 因此,在SqlSessionFactory类中应该有3个属性:1. 事务管理器对象 2. 数据源对象 3. map集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.deng.mybatis.mapper"/>
</mappers>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.god.ibatis.core;

import java.io.InputStream;

/**
* @author:dch
* @since 1.0
* @version 1.0
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder(){}

/**
* 解析核心配置文件创建SqlSessionFactory对象
* @param resource 核心配置文件
* @return 返回SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream resource){
SqlSessionFactory factory = new SqlSessionFactory();
return factory;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.god.ibatis.core;

import java.util.Map;

/**
* 一个SqlSessionFactory对应一个数据库
* 通过SqlSessionFactory可以创建一个或多个SqlSession对象
*/
public class SqlSessionFactory {
//事务管理器属性

//数据源属性

//mapper
private Map<String,SqlMapperStatement> mapperStatements;
}

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
package org.god.ibatis.core;
/**
* 这是一个pojo类,包含sql语句以及一个resultType属性(简化了,实际上还有其他属性)
*/
public class SqlMapperStatement {
private String sqlId;
private String resultType;

public SqlMapperStatement(){

}

public SqlMapperStatement(String sqlId,String resultType){
this.sqlId = sqlId;
this.resultType = resultType;
}

public String getSqlId() {
return sqlId;
}

public void setSqlId(String sqlId) {
this.sqlId = sqlId;
}

public String getResultType() {
return resultType;
}

public void setResultType(String resultType) {
this.resultType = resultType;
}

public String toString(){
return "sqlId=" + sqlId + " resultType=" + resultType;
}
}

第四步

抽取(设计)事务管理器属性。

我们知道在mybatis中有两种事务状态:JDBC和MANAGED。因此不能直接在SqlSessionFactory中写死或者采用都写上的方法(太不美观)。可以使用多态的方法,定义一个父接口transcation,让这两个类都去实现该接口,根据用户的参数动态绑定相关类和方法。在父接口中提供管理事务方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.god.ibatis.core;

/**
* 事务管理接口,其他事务类都要实现该接口
* 在该接口中定义事务管理的方法
* @author dch
* @version 1.0
* @since 1.0
*/
public interface Transaction {
void commit();

void rollback();

void close();
}
第五步

进一步思考:如何管理事务?需要通过connection对象,也就是说我们需要在事务管理器中获得connection对象。我们很快就想到了数据源,因为它的定义就是提供connection对象的都可以称为数据源。那么进一步思考,既然在事务管理器中有数据源对象,那么SqlSessionFactory中还有必要有数据源属性吗?当然没必要,因为其可以通过Transaction对象获取数据源。

进一步思考:如何设计数据源对象?由于数据源也有3个属性:POOLED、UNPOOLED和JNDI。因此也可以采用多态的方式,定义一个父接口。并且在jdk中已经定义了一个数据源接口:javax.sql.DataSource,任何数据源对象都要实现该接口,这是一个规范。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package org.god.ibatis.core;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
* 表示不使用数据库连接池,每次获得的connection对象都是新创建的
* @author dch
* @version 1.0
* @since 1.0
*/
public class UnPooledDataSource implements javax.sql.DataSource{
private String driver;
private String url;
private String username;
private String password;

public UnPooledDataSource(String driver,String url,String username,String password){
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.password = password;
this.url = url;
this.username = username;
}

@Override
public Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, username, password);

return c;
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}

@Override
public int getLoginTimeout() throws SQLException {
return 0;
}

@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}

@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}

@Override
public void setLoginTimeout(int seconds) throws SQLException {

}

@Override
public void setLogWriter(PrintWriter out) throws SQLException {

}

@Override
public boolean equals(Object obj) {
return super.equals(obj);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.god.ibatis.core;

import javax.sql.DataSource;

public class JdbcTransaction implements Transaction{
private DataSource dataSource;
//设置自动提交的标志,true表示自动提交,false表示不自动提交
private boolean autoCommit;
@Override
public void close() {

}

@Override
public void commit() {

}

@Override
public void rollback() {

}
}

第六步

完成SqlSessionFactoryBuilder的编写,测试能否成功创建SqlSessionFactory对象

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package org.god.ibatis.core;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author:dch
* @since 1.0
* @version 1.0
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder(){}

/**
* 解析核心配置文件创建SqlSessionFactory对象
* @param resource 核心配置文件
* @return 返回SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream resource){
SqlSessionFactory factory = null;
Transaction transaction = null;
DataSource dataSource = null;
Map<String,SqlMapperStatement>map = null;
//存放sqlMapper.xml文件的路径
List<String>sqlMapperXmlPath = new ArrayList<>();
try {
//解析核心配置文件
SAXReader reader = new SAXReader();
Document document = reader.read(resource);
//获得根标签
Element configuration = document.getRootElement();
//获得environments标签
Element environments = configuration.element("environments");
String aDefault = environments.attributeValue("default");
//environment[@id='"+Default+"'];
//Element environment = environments.element("environment[@id='" + aDefault + "']");
List<Element> environmentlist = environments.elements("environment");
Element transactionElm = null;
Element dataSourceElm = null;
//根据default属性获取对应的environment标签
for(int i = 0; i < environmentlist.size(); i ++){
Element e = environmentlist.get(i);
if(e.attributeValue("id").equals(aDefault)){
transactionElm = e.element("transactionManager");
dataSourceElm = e.element("dataSource");
break;
}
}
Element mappers = configuration.element("mappers");
List<Element> mapper = mappers.elements("mapper");
mapper.forEach(mappert ->{
String s = mappert.attributeValue("resource");
sqlMapperXmlPath.add(s);
});
dataSource = getDataSource(dataSourceElm);
transaction = getTransaction(transactionElm,dataSource);
map = getMap(sqlMapperXmlPath);
factory = new SqlSessionFactory(transaction,map);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}

/**
* 返回一个事务管理器对象,作为sqlSessionFactory的属性
* @param transactionElm 核心配置文件中的数据源标签
* @param dataSource 数据源对象作为其属性之一
* @return 返回一个事务管理器对象
*/
private Transaction getTransaction(Element transactionElm,DataSource dataSource){
Transaction transaction = null;
String type = transactionElm.attributeValue("type").trim().toUpperCase();

if (constant.JDBC_TRANSACTION.equals(type)) {
transaction = new JdbcTransaction(dataSource,false);
}else if(constant.MANAGED_TRANSACTION.equals(type)){

}

return transaction;
}

/**
* 解析核心配置文件,返回一个数据源对象
* @param dataSourceElm 核心配置文件中的数据源标签
* @return 返回数据源对象
*/
public DataSource getDataSource(Element dataSourceElm){
String type = dataSourceElm.attributeValue("type").trim().toUpperCase();
Map<String,String> map = new HashMap<>();
List<Element> properties = dataSourceElm.elements("property");
properties.forEach(property -> {
String name = property.attributeValue("name");
String value = property.attributeValue("value");
map.put(name,value);
});
DataSource dataSource = null;
if (constant.POOLED_DATASOURCE.equals(type)) {

}else if(constant.UNPOOLED_DATASOURCE.equals(type)){
dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"),map.get("password"));
}else if(constant.JNDI_DATASOURCE.equals(type)){

}

return dataSource;
}

/**
* 获取Map<String,SqlMapperStatement>集合作为SqlSessionFactory的属性
* @param sqlMapperXmlPath sqlMapper.xml文件的路径
* @return 返回一个map集合
*/
private Map<String,SqlMapperStatement> getMap(List<String>sqlMapperXmlPath){
Map<String,SqlMapperStatement>map = new HashMap<>();
sqlMapperXmlPath.forEach(path -> {
try {
//解析sqlMapper.xml文件
SAXReader reader = new SAXReader();
Document document = reader.read(Resources.getResource(path));
Element mapper = document.getRootElement();
String namespace = mapper.attributeValue("namespace");
List<Element> tags = mapper.elements();
tags.forEach(tag -> {
String id = tag.attributeValue("id");
String resultType = tag.attributeValue("resultType");
String sqlId = namespace + "." + id;
String sql = tag.getTextTrim();
SqlMapperStatement sqlMapperStatement = new SqlMapperStatement(sql, resultType);
map.put(sqlId,sqlMapperStatement);
});
} catch (Exception e) {
e.printStackTrace();
}
});

return map;
}
}
第七步

设计SqlSession类。通过SqlSessionFactory的openSession方法创建SqlSession对象。

因为需要通过SqlSession执行sql语句,所以SqlSession中必须要有connection对象和sql语句。这些都可以通过SqlSessionFactory对象获得,因此往SqlSession对象中传入SqlSessionFactory对象。

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
package org.god.ibatis.core;

import java.sql.Connection;
import java.util.Map;

public class SqlSession {
private SqlSessionFactory sqlSessionFactory;

public SqlSession(){}

public SqlSession(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory = sqlSessionFactory;
}

public void commit(){
sqlSessionFactory.getTransaction().commit();
}

public void rollback(){
sqlSessionFactory.getTransaction().rollback();
}

public void close(){
sqlSessionFactory.getTransaction().close();
}
}

第八步

在SqlSession类中编写执行sql语句的方法。这里只实现insert和selectOne方法

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package org.god.ibatis.core;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.Map;

public class SqlSession {
private SqlSessionFactory sqlSessionFactory;

public SqlSession(){}

public SqlSession(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory = sqlSessionFactory;
}

/**
* 插入sql语句
* @param sqlId
* @param object 插入数据库表的对象,要求对象属性为String类型,因为其他类型没有实现
* @return
*/
public int insert(String sqlId,Object object){
int count = 0;
try {
Connection connection = sqlSessionFactory.getTransaction().getConnection();
Map<String, SqlMapperStatement> mapperStatements = sqlSessionFactory.getMapperStatements();
SqlMapperStatement sqlMapperStatement = mapperStatements.get(sqlId);
String sql = sqlMapperStatement.getSql();
String sql1 = sql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement preparedStatement = connection.prepareStatement(sql1);
//给第几个?传什么值
int index = 1;
int fromIndex = 0;
while(true){
int jingIndex = sql.indexOf("#",fromIndex);
if(jingIndex < 0){
break;
}
int youkuoIndex = sql.indexOf("}");
//获取属性名
String propertyName = sql.substring(jingIndex + 2, youkuoIndex).trim();
//获取方法名
String methodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
//利用反射机制获取方法
Method getMethod = object.getClass().getDeclaredMethod(methodName);
//调用方法
Object propertyValue = getMethod.invoke(object);
//给?传值
preparedStatement.setString(index,propertyValue.toString());
index++;
}
count = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}

/**
* 查询sql语句,返回一条记录,本方法只考虑单条件的查询即?只有一个,且参数必须为String类型
* @param sqlId
* @param param
* @return
*/
public Object selectOne(String sqlId,Object param){
Object obj = null;
try {
SqlMapperStatement sqlMapperStatement = sqlSessionFactory.getMapperStatements().get(sqlId);
String godbatisSql = sqlMapperStatement.getSql();
String resultType = sqlMapperStatement.getResultType();

String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9]*}", "?");
Connection connection = sqlSessionFactory.getTransaction().getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//本框架只考虑参数为String类型的
ps.setString(1,param.toString());
ResultSet resultSet = ps.executeQuery();
if (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
Class<?> resultClass = Class.forName(resultType);
obj = resultClass.newInstance();
for(int i = 0; i <= columnCount; i ++){
String columnName = metaData.getColumnName(i + 1).trim();
//这里可以体现出为什么要求查询出来的数据库表的字段名和pojo类的属性名一致
//拼接set方法,给对象的属性赋值
String methodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1);
Method setMethod = obj.getClass().getDeclaredMethod(methodName, String.class);
setMethod.invoke(obj,resultSet.getString(columnName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}

public void commit(){
sqlSessionFactory.getTransaction().commit();
}

public void rollback(){
sqlSessionFactory.getTransaction().rollback();
}

public void close(){
sqlSessionFactory.getTransaction().close();
}
}