设计思路

使用者

  • 引入自定义的框架jar
  • 编写配置文件
    1. dbconfig.xml:数据库配置信息,存放mapper.xml位置
    2. mapper.xml:sql配置信息

框架

  • 加载配置文件:以字节流存储在内存中
    • 创建Resources类:InputStrean getResourceAsStream(String path)
  • 创建两个Bean:容器对象,存放配置文件解析结果
    • Configuration:核心配置类,dbconfig.xml解析结果
    • MapperStatement:映射配置类,mapper.xml解析结果
  • 解析配置文件:dom4j
    • 创建SqlSessionFactoryBuilder:build(InputStream in)
    • 使用dom4j解析配置文件,将结果存放在容器对象
    • 创建SqlSessionFactory对象:生产SqlSession会话对象(工厂模式)
  • 创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
    • openSession():生产sqlSession
  • 创建SqlSession接口及实现类DefaultSqlSession
    • 定义crud操作
      • selectList()
      • selectOne()
      • update()
      • delete()
  • 创建Executor接口及实现类SimpleExecutor
    • query(Configuration, MapperStatement, Object… params):执行JDBC代码

实现

测试项目创建

  • 创建IPersistence_test项目

  • 在resources目录下新建dbconfig.xml配置文件,填写数据源信息
1
2
3
4
5
6
7
8
9
<!--dbconfig.xml-->
<configuration>
<dataSource>
<property name="driver-class" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</configuration>
  • 然后编写mapper.xml保存具体的sql
1
2
3
4
5
6
7
8
9
10
11
12
<mapper namespace="user">

<!--sql唯一标识:namespace.id组成:statementId-->
<select id="findAll" resultType="com.chaofan.pojo.User">
select * from user
</select>

<select id="findOne" paramType="com.chaofan.pojo.User" resultType="com.chaofan.pojo.User">
select * from user where id=#{id} and username=#{username}
</select>

</mapper>

使用namespace来区分不同的mapper.xml文件

在父标签内部,按照sql的不同,选择不同的标签来区别,再通过id属性来区分每一个标签,然后按照namespace.id组成每一个sql的唯一标识

  • 最后在dbconfig.xml中指定mapper.xml的位置
1
<mappers resource="UserMapper.xml"/>

使用端暂时结束,然后开始编写库

目前配置文件总览

1
2
3
4
5
6
7
8
9
10
11
<!--dbconfig.xml-->
<configuration>
<dataSource>
<property name="driver-class" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
<!--mapper.xml位置-->
<mappers resource="UserMapper.xml"/>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--UserMapper.xml-->
<mapper namespace="user">

<!--sql唯一标识:namespace.id组成:statementId-->
<select id="findAll" resultType="com.chaofan.pojo.User">
select * from user
</select>

<select id="findOne" paramType="com.chaofan.pojo.User" resultType="com.chaofan.pojo.User">
select * from user where id=#{id} and username=#{username}
</select>

</mapper>

自定义框架的实现

创建项目

新建一个IPersistence模块

新建Resources类

编写Resources类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.chaofan.io;

import java.io.InputStream;

/**
* @author Fairy
* @date 2021/11/8
*/
public class Resources {

/**
* 根据path加载配置文件
* @param path 配置文件路径
* @return InputStream
*/
public static InputStream getResourceAsStream(String path){
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}

非常简单,获得当前类的类加载器然后加载配置文件获得输入流

容器对象定义

Configuration核心配置类

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 com.chaofan.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
* 核心配置类
* @author Fairy
* @date 2021/11/8
* @since 1.0
*/
public class Configuration {

private DataSource dataSource;

/**
* key:statementId
* value:封装好的MappedStatement
*/
Map<String, MappedStatement> mappedStatementMap = new HashMap<>();

public DataSource getDataSource() {
return dataSource;
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

public Map<String, MappedStatement> getMappedStatementMap() {
return mappedStatementMap;
}

public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
}

MappedStatement解析出来的Mapper对象

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
package com.chaofan.pojo;

/**
* 解析出来的Mapper对象
* @author Fairy
* @date 2021/11/8
* @since 1.0
*/
public class MappedStatement {

/**
* id标识
*/
private String id;

/**
* 返回值类型
*/
private String resultType;

/**
* 参数类型
*/
private String paramType;

/**
* sql语句
*/
private String sql;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getResultType() {
return resultType;
}

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

public String getParamType() {
return paramType;
}

public void setParamType(String paramType) {
this.paramType = paramType;
}

public String getSql() {
return sql;
}

public void setSql(String sql) {
this.sql = sql;
}
}

编写配置文件解析类

解析配置文件。保存在Configuration类的实例中

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
package com.chaofan.config;

import com.chaofan.io.Resources;
import com.chaofan.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
* @author Fairy
* @date 2021/11/8
*/
public class XMLConfigBuilder {

private final Configuration configuration;

public XMLConfigBuilder() {
this.configuration = new Configuration();
}

/**
* 解析配置文件,封装Configuration
* 使用dom4j
* @param in InputStream
* @return Configuration
*/
@SuppressWarnings("unchecked")
public Configuration parseConfig(InputStream in) throws Exception {
Document document = new SAXReader().read(in);
//<configuration>
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : list) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name, value);
}

ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl(properties.getProperty("url"));
dataSource.setDriverClass(properties.getProperty("driver-class"));
dataSource.setUser(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(dataSource);

//解析mapper.xml
//拿到路径
List<Element> mapperList = rootElement.selectNodes("//mapper");
//获得流并解析
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream mapperStream = Resources.getResourceAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(mapperStream);
}
return configuration;
}
}

使用xpath获取所有的<property>标签,然后遍历获取到的List,保存在Properties类的对象中

也可以用Map存储

Mapper解析类

解析mapper.xml的每一个sql语句,封装到MappedStatement对象中

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
package com.chaofan.config;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
* @author Fairy
* @date 2021/11/9
*/
public class XMLMapperBuilder {

private final Configuration configuration;

public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}

/**
* 把Mappers存到Configuration中
* @param inputStream mappers流
*/
@SuppressWarnings("unchecked")
public void parse(InputStream inputStream) throws Exception {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> selectList = rootElement.selectNodes("//select");
for (Element element : selectList) {
//获取每个属性
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramType = element.attributeValue("paramType");
String sqlText = element.getTextTrim();
//封装到MappedStatement
MappedStatement statement = new MappedStatement();
statement.setId(id);
statement.setResultType(resultType);
statement.setParamType(paramType);
statement.setSql(sqlText);
String key = namespace + "." + id;
//保存在map中
configuration.getMappedStatementMap().put(key, statement);
}
}
}

编写SqlSessionFactoryBuilder类

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
package com.chaofan.session;

import com.chaofan.config.XMLConfigBuilder;
import com.chaofan.pojo.Configuration;

import java.io.InputStream;

/**
* @author Fairy
* @date 2021/11/8
*/
public class SqlSessionFactoryBuilder {

/**
* 构建SqlSessionFactory
* @param in 主配置文件流
* @return DefaultSqlSessionFactory
*/
public SqlSessionFactory build(InputStream in) throws Exception {
//使用dom4j解析配置文件,封装到Configuration
XMLConfigBuilder builder = new XMLConfigBuilder();
Configuration configuration = builder.parseConfig(in);
//创建SqlSessionFactory对象:生产SqlSession
return new DefaultSqlSessionFactory(configuration);
}
}

把InputStream传给XMLConfigBuilder类解析成Configuration对象,然后返回DefaultSqlSessionFactory对象

SqlSessionFactory接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.chaofan.session;

/**
* @author Fairy
* @date 2021/11/8
*/
public interface SqlSessionFactory {

/**
* 生产一个SqlSession
* @return SqlSession
*/
SqlSession openSession();
}

生产SqlSession,然后编写默认实现类

DefaultSqlSessionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.chaofan.session;

import com.chaofan.pojo.Configuration;

/**
* @author Fairy
* @date 2021/11/9
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {

private final Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}

@Override
public SqlSession openSession() {
return new DefaultSqlSession();
}
}

这个工厂类生产SqlSession对象

SqlSession接口定义

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
package com.chaofan.session;

import java.util.List;

/**
* @author Fairy
* @date 2021/11/9
*/
public interface SqlSession {

/**
* 查询多个
* @param statementId sql的id
* @param params 参数列表
* @param <E> 返回类型
* @return List<E>
*/
<E> List<E> selectList(String statementId, Object... params) throws Exception;

/**
* 按条件查询单个
* @param statementId sql的id
* @param params 参数列表
* @param <T> 返回类型
* @return T
*/
<T> T selectOne(String statementId, Object... params) throws Exception;

/**
* 更新内容
* @param statementId sql的id
* @param params 参数列表
* @return 受影响的行数
*/
int update(String statementId, Object params) throws Exception;

/**
* 保存对象
* @param statementId sql的id
* @param params 参数列表
* @return 受影响的行数
*/
int save(String statementId, Object params) throws Exception;

/**
* 删除对象
* @param statementId sql的id
* @param params 参数列表
* @return 受影响的行数
*/
int delete(String statementId, Object params) throws Exception;

/**
* 关闭连接
*/
void close();
}

编写SqlSession实现类DefaultSqlSession

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
package com.chaofan.session;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;

import java.util.List;

/**
* @author Fairy
* @date 2021/11/9
*/
public class DefaultSqlSession implements SqlSession {

private final Configuration configuration;

private Executor executor;

public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}

@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
//调动simpleExecutor的query方法
executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return executor.query(configuration, mappedStatement, params);
}

@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<T> t = selectList(statementId, params);
if (t.size() == 1) {
return t.get(0);
} else if (t.size() > 1){
throw new RuntimeException("返回结果过多");
}
throw new RuntimeException("返回结果为空");
}

@Override
public int update(String statementId, Object params) throws Exception {
executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return executor.update(configuration, mappedStatement, params);
}

@Override
public int save(String statementId, Object params) throws Exception {
executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return executor.update(configuration, mappedStatement, params);
}

@Override
public int delete(String statementId, Object params) throws Exception {
executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return executor.update(configuration, mappedStatement, params);
}

@Override
public void close() {
try {
executor.close();
}catch(Exception ignore){}
}

}

Executer接口执行sql

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 com.chaofan.session;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

/**
* @author Fairy
* @date 2021/11/10
*/
public interface Executor {

/**
* 查找
* @param configuration 核心配置类
* @param mappedStatement 已封装的sql
* @param params 参数
* @param <E> 返回值类型
* @return 结果
*/
<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

/**
* 更新
* @param configuration 核心配置类
* @param mappedStatement 已封装的sql
* @param params 参数
* @return 受影响的行数
*/
int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

/**
* 关闭连接
*/
void close();
}

编写实现类

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
package com.chaofan.session;

import com.chaofan.config.BoundSql;
import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;
import com.chaofan.utils.GenericTokenParser;
import com.chaofan.utils.ParameterMapping;
import com.chaofan.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**L
* @author Fairy
* @date 2021/11/10
*/
public class SimpleExecutor implements Executor {
private PreparedStatement statement;
private Connection connection;
@Override
@SuppressWarnings({"unchecked", "Duplicates"})
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
//注册驱动,获取连接
connection = configuration.getDataSource().getConnection();
//获取sql
String sql = mappedStatement.getSql();
//转换sql
BoundSql boundSql = getBoundSql(sql);
//获取预处理对象
statement = connection.prepareStatement(boundSql.getSqlText());
//设置参数
String paramType = mappedStatement.getParamType();
Class<?> type = getClassType(paramType);
List<ParameterMapping> mappingList = boundSql.getParameterMappingList();
for (int i = 0; i < mappingList.size(); i++) {
ParameterMapping parameterMapping = mappingList.get(i);
String content = parameterMapping.getContent();
Field field = type.getDeclaredField(content);
//开启暴力访问
field.setAccessible(true);
E o = (E)field.get(params[0]);
//i从0开始,所以加一
statement.setObject(i+1, o);
}
//执行sql
ResultSet resultSet = statement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
Object o = resultTypeClass.getDeclaredConstructor().newInstance();
//封装结果集
List<Object> objects = new ArrayList<>();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 0; i < metaData.getColumnCount(); i++) {
//获取字段名
String columnName = metaData.getColumnName(i+1);
Object value = resultSet.getObject(columnName);
//根据表和实体关系,完成封装,内省
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, value);
}
objects.add(o);
}
return (List<E>) objects;
}

@Override
@SuppressWarnings("Duplicates")
public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
connection = configuration.getDataSource().getConnection();
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
statement = connection.prepareStatement(boundSql.getSqlText());
//设置参数
String paramType = mappedStatement.getParamType();
Class<?> type = getClassType(paramType);
List<ParameterMapping> mappingList = boundSql.getParameterMappingList();
for (int i = 0; i < mappingList.size(); i++) {
ParameterMapping parameterMapping = mappingList.get(i);
String content = parameterMapping.getContent();
Field field = type.getDeclaredField(content);
//开启暴力访问
field.setAccessible(true);
Object o = field.get(params[0]);
//i从0开始,所以加一
statement.setObject(i+1, o);
}
//执行sql
return statement.executeUpdate();
}

@Override
public void close() {
try {
statement.close();
connection.close();
} catch (SQLException ignored) {}
}

private Class<?> getClassType(String paramType) throws ClassNotFoundException {
if (paramType != null) {
return Class.forName(paramType);
}
throw new NullPointerException("paramType is null");
}

/**
* 把 #{} 转换为 ?
* 解析出#{}的值
* @param sql 待处理的sql
* @return 封装后的BoundSql
*/
private BoundSql getBoundSql(String sql) {
//标记处理类
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//解析出来的参数名
List<ParameterMapping> mappings = tokenHandler.getParameterMappings();
return new BoundSql(parseSql, mappings);
}
}

最后测试一下

1
2
3
4
5
6
7
8
9
InputStream inputStream = Resources.getResourceAsStream("dbconfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

User user = new User();
user.setId("1");
user.setName("张三");
User result = sqlSession.selectOne("user.findOne", user);
System.out.println(result);

补充

根据接口动态生成实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> mapperClass) {
//jdk动态代理
Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
//获取返回值类型
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
return selectList(statementId, args);
}
return selectOne(statementId, args);
});
return (T) o;
}