背景
写了一个简易的mybatis,实现了通过mybatis代理增强Dao层的基本逻辑,这里梳理一下这个Demo的执行流程
核心流程
1.用户通过加载核心配置文件获得输入流,放在内存中
2.通过流信息构建工厂
3.通过工厂打开SqlSession对象
4.SqlSession对象通过getMapper方法获得被代理的接口对象
5.那个被代理的接口对象有增强的方法,通过这个对象操作数据库
实现细节
包结构
1.自定义mybatis包结构
config
BoundSql :存储解析过的SQL文本;成员变量->sqlText(解析过后的sql),parameterMappingList(参数)
XMLConfigBuilder :解析xml的类;成员变量->Configuration
XMLMapperBuilder :解析xml中mapper的类,调用时机是在XMLConfigBuilder.build() 执行时;成员变量->Configuration
io
Resources :根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
pojo
Configuration :核心配置对象会把sqlMapConfig.xml中的信息解析在这个对象里;成员变量->dataSource(数据源信息),Map<String,MappedStatement>(key: statementid value:封装好的mappedStatement对象)
MappedStatement :Mapper中的SQL解析对象,会把用户定义的Mapper里的信息放在这里面;成员变量->id(id表识),resultType(返回值类型),parameterType(参数类型),sql(sql语句) {以上成员变量类型都是String
}
sqlSession
SqlSessionFactory:规定了SqlSessionFactory的基本功能openSession()
DefaultSqlSessionFactory:实现了SqlSessionFactory的基本功能openSession()
SqlSessionFactoryBuilder:通过build方法通过流对象创建出DefaultSqlSessionFactory对象
SqlSession:规定了SqlSession的基本功能getMapper()
DefaultSqlSession:实现了SqlSession的基本功能getMapper()。其getMapper核心是用执行器Executor完成的。
Executor:与数据库交互的对象。规定了基本功能(Configuration configuration, MappedStatement mappedStatement, Object... params)
simpleExecutor:通过前面构建出来的configuration,MappedStatement ,params实现了基本功能query。
2.用户包结构
和Mybatis的结构一样~
几处核心流程分析
1.读取配置文件里的信息到configuration对象中
1.用户调用SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);创建对象
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
// 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(in);
// 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
2.Configuration configuration = xmlConfigBuilder.parseConfig(in);
/**
* 该方法就是使用dom4j对配置文件进行解析,封装Configuration
*/
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
//<configuration>
Element rootElement = document.getRootElement();
//读取property节点
List<Element> list = rootElement.selectNodes("//property");
Properties properties = new Properties();
//读取property节点下的Element信息
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.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(comboPooledDataSource);
//mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
List<Element> mapperList = rootElement.selectNodes("//mapper");
//对于每一个mapper标签下
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
//新建一个XMLMapperBuilder对象来解析,用的是一个configuration对象
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsSteam);
}
3.xmlMapperBuilder.parse(resourceAsSteam);
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
//分别添加增删改查节点,让其可以解析这些节点信息
addNode(rootElement,"select");
addNode(rootElement,"insert");
addNode(rootElement,"update");
addNode(rootElement,"delete");
}
public void addNode(Element rootElement,String nodeName){
String namespace = rootElement.attributeValue("namespace");
List<Element> list = rootElement.selectNodes("//"+nodeName);
for (Element element : list) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterType = element.attributeValue("paramterType");
String sqlText = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
String key = namespace+"."+id;
//放到configuration里
configuration.getMappedStatementMap().put(key,mappedStatement);
System.out.println("put " + key + " : " + mappedStatement.toString());
}
}
至此,Configuration里的两个对象已经填充完毕。
2.通过getMapper获得代理对象
1.用户调用SqlSession的getMapper方法为Dao接口生成代理类(MyBatis是提前就生成好代理类了)。
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
//将要去完成对simpleExecutor里的query方法的调用
simpleExecutor simpleExecutor = new simpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
//有可能执行insert方法
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
public <T> T selectOne(String statementid, Object... params) throws Exception {
List<Object> objects = selectList(statementid, params);
if(objects.size()==1){
return (T) objects.get(0);
}else {
throw new RuntimeException("查询结果为空或者返回结果过多");
}
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
// 方法名:findAll
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
}
2.这其中的simpleExecutor.query(configuration, mappedStatement, params);方法与通过之前生成的configuration和mappedStatement和params与数据库交互。
public class simpleExecutor implements Executor {
@Override //user
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. 设置参数
//获取到了参数的全路径
String paramterType = mappedStatement.getParamterType();
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);
}
//增加,删除,修改
if("insert".equals(sql.trim().split(" ")[0]) ||
"delete".equals(sql.trim().split(" ")[0]) ||
"update".equals(sql.trim().split(" ")[0])){
int i = preparedStatement.executeUpdate();
if(i == 0){
throw new RuntimeException("操作失败,影响条数为0条");
}
else {
System.out.println("操作成功!");
List<Integer> list = new ArrayList<>();
list.add(i);
return (List<E>)list;
}
}
// 5. 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
ArrayList<Object> objects = new ArrayList<>();
// 6. 封装返回结果集
while (resultSet.next()){
Object o =resultTypeClass.newInstance();
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名
String columnName = metaData.getColumnName(i);
// 字段的值
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;
}
private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
if(paramterType!=null){
Class<?> aClass = Class.forName(paramterType);
return aClass;
}
return null;
}
/**
* 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
//标记处理类:配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//#{}里面解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
return boundSql;
}
}
关于MyBatis其他的一点小说明
关于SqlSession对象selectList和getMapper两种方法的差别:
selectList,selectOne等都是通过StatmentMapper直接对象直接进行查询的。
getMapper则是把Dao对象代理了一下在执行selectList
结束