自定义MyBatis是为了深入了解MyBatis的原理
主要的调用是这样的:
//1.读取配置文件InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.创建SqlSessionFactory工厂SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory factory = builder.build(in);//3.使用工厂生产SqlSession对象SqlSession session = factory.openSession();//4.使用SQLSession创建Dao接口的代理对象UserDao userDao = session.getMapper(UserDao.class);//5.使用代理对象执行方法List<User> users = userDao.findAll();for (User user : users) { System.out.println(user);}//6.释放资源session.close();in.close();
首先第一步:将配置文件SqlMapConfig.xml转为流文件
<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE configuration>
<configuration> <!--配置环境--> <environments default="mysql"> <!--配置mysql的环境--> <environment id="mysql"> <!--配置事务类型--> <transactionManager type="JDBC"></transactionManager> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件--> <!--<mappers> <mapper resource="com/jinke/dao/UserDao.xml"/> </mappers>--> <!--如果是用注解来配置--> <mappers> <mapper class="com.jinke.dao.UserDao"/> </mappers></configuration>
import java.io.InputStream;
/*使用类加载器读取配置文件的类*/public class Resources {
public static InputStream getResourceAsStream(String filePath) { return Resources.class.getClassLoader().getResourceAsStream(filePath); }}
第二步:解析配置文件
import com.jinke.mybatis.cfg.Configuration;import com.jinke.mybatis.sqlsession.defaults.DefaultSqlSessionFactory;import com.jinke.mybatis.utils.XMLConfigBuilder;
import java.io.InputStream;
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream config) { Configuration cfg = XMLConfigBuilder.loadConfiguration(config); return new DefaultSqlSessionFactory(cfg); }}
主要是通过反射将属性值保存到map中
import com.jinke.mybatis.annotations.Select;import com.jinke.mybatis.cfg.Configuration;import com.jinke.mybatis.cfg.Mapper;import com.jinke.mybatis.io.Resources;import org.dom4j.Attribute;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;
import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.HashMap;import java.util.List;import java.util.Map;
public class XMLConfigBuilder { public static Configuration loadConfiguration(InputStream config) { Configuration cfg = new Configuration(); try {
SAXReader reader = new SAXReader();
Document document = reader.read(config);
Element root = document.getRootElement(); List<Element> propertyElements = root.selectNodes("//property");
for (Element propertyElement : propertyElements) { String name = propertyElement.attributeValue("name"); if ("driver".equals(name)) { String driver = propertyElement.attributeValue("value"); cfg.setDriver(driver); } if ("url".equals(name)) { String url = propertyElement.attributeValue("value"); cfg.setUrl(url); } if ("username".equals(name)) { String username = propertyElement.attributeValue("value"); cfg.setUsername(username); } if ("password".equals(name)) { String password = propertyElement.attributeValue("value"); cfg.setPassword(password); } } List<Element> mapperElements = root.selectNodes("//mappers/mapper"); for (Element mapperElement : mapperElements) { Attribute attribute = mapperElement.attribute("resource"); if (attribute != null) { System.out.println("使用的是XML"); String mapperPath = attribute.getValue(); Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath); cfg.setMappers(mappers); } else { System.out.println("使用的是注解"); String daoClassPath = mapperElement.attributeValue("class"); Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath); cfg.setMappers(mappers); } } return cfg; } catch (Exception e) { e.printStackTrace(); } finally { try { config.close(); } catch (IOException e) { e.printStackTrace(); } } return cfg; }
private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws IOException { InputStream in = null; Map<String, Mapper> mappers = new HashMap<String, Mapper>(); try { in = Resources.getResourceAsStream(mapperPath); SAXReader reader = new SAXReader(); Document document = reader.read(in); Element root = document.getRootElement(); String namespace = root.attributeValue("namespace"); List<Element> selectElements = root.selectNodes("//select"); for (Element selectElement : selectElements) { String id = selectElement.attributeValue("id"); String resultType = selectElement.attributeValue("resultType"); String queryString = selectElement.getText(); String key = namespace + "." + id; Mapper mapper = new Mapper(); mapper.setQueryString(queryString); mapper.setResultType(resultType); mappers.put(key, mapper); } return mappers; } catch (Exception e) { e.printStackTrace(); } return mappers; }
private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception { Map<String, Mapper> mappers = new HashMap<String, Mapper>(); Class daoClass = Class.forName(daoClassPath); Method[] methods = daoClass.getMethods(); for (Method method : methods) { boolean isAnnotated = method.isAnnotationPresent(Select.class); if (isAnnotated) { Mapper mapper = new Mapper(); Select selectAnno = method.getAnnotation(Select.class); String queryString = selectAnno.value(); mapper.setQueryString(queryString); Type type = method.getGenericReturnType(); if (type instanceof ParameterizedType) { ParameterizedType ptype = (ParameterizedType) type; Type[] types = ptype.getActualTypeArguments(); Class domainClass = (Class) types[0]; String resultType = domainClass.getName(); mapper.setResultType(resultType); } String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className + "." + methodName; mappers.put(key, mapper); } } return mappers; }}
第三步:DefaultSqlSessionFactory工厂生产出DefaultSqlSession对象
import com.jinke.mybatis.cfg.Configuration;import com.jinke.mybatis.sqlsession.SqlSession;import com.jinke.mybatis.sqlsession.SqlSessionFactory;
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg) { this.cfg = cfg; }
public SqlSession openSession() { return new DefaultSqlSession(cfg); }}
第四步:DefaultSqlSession执行动态代理
import com.jinke.mybatis.cfg.Configuration;import com.jinke.mybatis.sqlsession.SqlSession;import com.jinke.mybatis.sqlsession.proxy.MapperProxy;import com.jinke.mybatis.utils.DataSourceUtil;
import java.lang.reflect.Proxy;import java.sql.Connection;import java.sql.SQLException;
public class DefaultSqlSession implements SqlSession {
private Configuration cfg; private Connection connection;
public DefaultSqlSession(Configuration cfg) { this.cfg = cfg; this.connection = DataSourceUtil.getConnection(cfg); }
public <T> T getMapper(Class<T> daoInterfaceClass) { return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection)); }
public void close() { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }}
执行sql语句
import com.jinke.mybatis.cfg.Mapper;import com.jinke.mybatis.utils.Executor;
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.sql.Connection;import java.util.Map;
public class MapperProxy implements InvocationHandler {
private Map<String, Mapper> mappers; private Connection connection;
public MapperProxy(Map<String, Mapper> mappers, Connection connection) { this.mappers = mappers; this.connection = connection; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className + "." + methodName; Mapper mapper = mappers.get(key); if (mapper == null) { throw new IllegalArgumentException("传入的参数有误"); } return new Executor().selectList(mapper, connection); }}
import com.jinke.mybatis.cfg.Mapper;
import java.beans.PropertyDescriptor;import java.lang.reflect.Method;import java.sql.*;import java.util.ArrayList;import java.util.List;
public class Executor { public <E> List<E> selectList(Mapper mapper, Connection conn) { PreparedStatement pstm = null; ResultSet rs = null; try { String queryString = mapper.getQueryString(); String resultType = mapper.getResultType(); Class domainClass = Class.forName(resultType); pstm = conn.prepareStatement(queryString); rs = pstm.executeQuery(); List<E> list = new ArrayList<E>(); while (rs.next()) { E obj = (E) domainClass.newInstance(); ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); for (int i = 1; i < columnCount; i++) { String columnName = rsmd.getColumnName(i); Object columnValue = rs.getObject(columnName); PropertyDescriptor pd = new PropertyDescriptor(columnName, domainClass); Method writeMethod = pd.getWriteMethod(); writeMethod.invoke(obj, columnValue); } list.add(obj); } return list; } catch (Exception e) { e.printStackTrace(); } finally { release(pstm, rs); } return null; }
private void release(PreparedStatement pstm, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } }
if (pstm != null) { try { pstm.close(); } catch (SQLException e) { e.printStackTrace(); } } }}
最后放一张文件结构图
代码地址 https://github.com/king1039/MyBatis