自定义迷你版mybatis框架实现增删改查
一.实现思路
相信大家对于mybatis已经用的比较多啦那么我们应该去实现自定义的框架呢我们可以从以下步骤去考虑:
使用端:
提供核心配置文件:
mapperConfig.xml : 存放数据源信息,引入mapper.xml
UserSqlMapper.xml : sql语句的配置文件信息
框架端:
1.读取配置文件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可
以创建javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯一标识,Mapper> 唯一标识:namespace + “.” + id
(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型
2.解析配置文件
创建sqlSessionFactoryBuilder类:
方法:sqlSessionFactory build():
第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
第二:创建SqlSessionFactory的实现类DefaultSqlSession
3.创建SqlSessionFactory:
方法:openSession() : 获取sqlSession接口的实现类实例对象
4.创建sqlSession接口及实现类:主要封装crud方法
方法:selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作
涉及到的设计模式:
Builder构建者设计模式、工厂模式、代理模式
二.主要代码实现逻辑
//dom4j解析核心配置文件
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
Element documentRootElement = document.getRootElement();
//封装数据库参数
List<Element> list = documentRootElement.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 comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
//存储数据源到全局配置对象Configuration
configuration.setDataSource(comboPooledDataSource);
//
List<Element> mapperList = documentRootElement.selectNodes("//mapper");
for (Element element : mapperList) {
//解析映射文件
String resourcePath = element.attributeValue("resource");
InputStream resourceAsStream = Resources.getResourceAsStream(resourcePath);
XMLMapperBuiler xmlMapperBuiler = new XMLMapperBuiler(configuration);
xmlMapperBuiler.parseXMLConfig(resourceAsStream);
}
return configuration;
}
//解析映射文件节点封装到全局配置对象configuration中
public void parseXMLConfig(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element documentRootElement = document.getRootElement();
List<Element> elementList = documentRootElement.elements();
// List<Element> elementList = documentRootElement.selectNodes("//select");
String namespace = documentRootElement.attributeValue("namespace");
for (Element element : elementList) {
String paramterType = element.attributeValue("paramterType");
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParamterType(paramterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
Map<String, MappedStatement> statementMap = configuration.getStatementMap();
statementMap.put(namespace + "." + id, mappedStatement);
configuration.setStatementMap(statementMap);
}
}
public <E> List<E> query(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
//获取jdbc连接
Connection connection = configuration.getDataSource().getConnection();
//获取原始sql
String sql = statement.getSql();
//获取封装好的要发送的sql语句与参数
BoundSql boundSql = boundSql(sql);
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
String paramterType = statement.getParamterType();
String resultType = statement.getResultType();
Class<?> paramterTypeClass = getClassType(paramterType);
//对参数赋值
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
Field declaredField = paramterTypeClass.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i + 1, o);
}
Class<?> resultTypeClass = getClassType(resultType);
ResultSet resultSet = preparedStatement.executeQuery();
ArrayList<Object> objects = new ArrayList<>();
// 遍历结果集并封装返回
while (resultSet.next()) {
Object o = resultTypeClass.newInstance();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
String columnName = resultSetMetaData.getColumnName(i);
Object object = resultSet.getObject(columnName);
//可以使用缺省的方法设置对象参数值
/*PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, object);*/
Field declaredField = resultTypeClass.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(o, object);
}
objects.add(o);
}
return (List<E>) objects;
}
//动态代理方式调用方法实现增删改查 对于怎么判断是查询还是修改可以根据映射文件sql标签来判断,
//判断查询的话可以根据返回参数类型如果是集合就是查询多条,我这里就没有去扩展判断了有兴趣的哥们可以自己试试玩一下
public <T> T getMapper(Class<?> mapperClass) {
Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
if (SqlType.SELECT_LIST.getMethod().equals(method.getName())) {
return selectList(className + "." + methodName, args);
} else if (SqlType.SELECT_ID.getMethod().equals(method.getName())) {
return selectOne(className + "." + methodName, args);
} else if (SqlType.UPDATE_TABLE.getMethod().equals(method.getName()) || SqlType.UPDATE_ID.getMethod().equals(method.getName())) {
return updateTable(className + "." + methodName, args);
} else if (SqlType.DELETE_ID.getMethod().equals(method.getName()) || SqlType.DELETE_TABLE.getMethod().equals(method.getName())) {
return deleteTable(className + "." + methodName, args);
}
throw new RuntimeException("方法调用错误");
}
});
return (T) o;
}
三.实现效果演示
传统方式
动态代理方式
四.总结
通过以上思路到实现,我们可以大概清楚mybatis是怎么实现了相关功能了那么我们需要注意的是数据源信息需要换成自己本地的,希望本次案例可以帮助大家看源码的时候容易理解源码的思路,更重要的是理解源码的思路可以帮助我们去排查框架使用端的报错,提升我们的开发效率。最后希望大家共勉欢迎探讨技术。
代码工程分为框架端与测试使用端两个
代码地址:https://gitee.com/xiangaiya/csdn/tree/master/IPersistence
https://gitee.com/xiangaiya/csdn/tree/master/IPersistence_test