手写Mybatis框架
一、简介
Mybatis是一款优秀的持久层框架,在互联网公司的项目开发中使用非常广泛。通过对MyBatis源码的学习,可以更好的了解Mybatis的使用,同时也可以借鉴其中优秀的编程方式和设计模式。学习是一个抽象归纳然后再运用的过程,通过对Mybatis源码核心部分的抽象,手写一个Mybatis框架,来更好的学习Mybatis。
二、处理流程
Mybatis核心流程分为三步:
1.读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化工作;
2.封装iBatis的编程模式,使用mapper接口开发的初始化工作;
3.通过SqlSession完成SQL的解析,参数的映射、SQL的执行、结果的反射解析过程;
三、手写Mybatis具体实现
1. 实体相关类
Dept.java:
package com.taoxi.mybatis.mysimple.entity; public class Dept { private Integer deptNo; private String dName; private String loc; public Integer getDeptNo() { return deptNo; } public void setDeptNo(Integer deptNo) { this.deptNo = deptNo; } public String getDname() { return dName; } public void setDname(String dname) { this.dName = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } }
DeptMapper.java:
package com.taoxi.mybatis.mysimple.mapper; import com.taoxi.mybatis.mysimple.entity.Dept; public interface DeptMapper { Dept deptFindById(Integer deptId); }
DeptMapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.taoxi.mybatis.mysimple.mapper.DeptMapper"> <select id="deptFindById" resultType="com.taoxi.mybatis.mysimple.entity.Dept"> select * from dept where deptno= ? </select> </mapper>
2.配置相关类
Configuration.java:
package com.taoxi.mybatis.mysimple.config; import java.util.HashMap; import java.util.Map; public class Configuration { private String jdbcDriver; private String jdbcUrl; private String jdbcUsername; private String jdbcPassword; private Map<String, MappedStatement> mappedStatments = new HashMap<String, MappedStatement>(); public String getJdbcDriver() { return jdbcDriver; } public void setJdbcDriver(String jdbcDriver) { this.jdbcDriver = jdbcDriver; } public String getJdbcUrl() { return jdbcUrl; } public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; } public String getJdbcUsername() { return jdbcUsername; } public void setJdbcUsername(String jdbcUsername) { this.jdbcUsername = jdbcUsername; } public String getJdbcPassword() { return jdbcPassword; } public void setJdbcPassword(String jdbcPassword) { this.jdbcPassword = jdbcPassword; } public Map<String, MappedStatement> getMappedStatments() { return mappedStatments; } public void setMappedStatments(Map<String, MappedStatement> mappedStatments) { this.mappedStatments = mappedStatments; } }
MappedStatemant.java:
package com.taoxi.mybatis.mysimple.config; public class MappedStatement { private String namespace; private String sourceId; private String resultType; private String sql; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getSourceId() { return sourceId; } public void setSourceId(String sourceId) { this.sourceId = sourceId; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } }
3.SqlSessionFactory工厂类
SqlSessionFactory.java:
package com.taoxi.mybatis.mysimple.session; import com.taoxi.mybatis.mysimple.config.Configuration; import com.taoxi.mybatis.mysimple.config.MappedStatement; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.List; import java.util.Properties; public class SqlSessionFactory { private final Configuration configuration = new Configuration(); public SqlSessionFactory() { loadDbInfo(); loadMappersInfo(); } public SqlSession openSession() { return new DefaultSqlSession(configuration); } //记录mapper xml文件存放的位置 public static final String MAPPER_CONFIG_LOCATION = "mappers"; //记录数据库连接信息文件存放位置 public static final String DB_CONFIG_FILE = "db.properties"; //加载数据库配置信息 private void loadDbInfo() { //加载数据库信息配置文件 InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE); Properties properties = new Properties(); try { properties.load(dbIn); } catch (IOException e) { throw new RuntimeException("load db info error:", e); } //将数据库配置信息写入configuration对象 configuration.setJdbcDriver(properties.get("jdbc.driver").toString()); configuration.setJdbcPassword(properties.get("jdbc.password").toString()); configuration.setJdbcUrl(properties.get("jdbc.url").toString()); configuration.setJdbcUsername(properties.get("jdbc.username").toString()); } //加载指定文件夹下的所有mapper.xml private void loadMappersInfo() { URL resources = null; resources = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION); File mappers = new File(resources.getFile()); if (mappers.isDirectory()){ File[] listFiles = mappers.listFiles(); //遍历文件夹下所有的mapper.xml,解析信息后,注册到configuration对象中 for (File file : listFiles) { loadMapperInfo(file); } } } //加载指定的mapper.xml文件 private void loadMapperInfo(File file) { //创建saxReader对象 SAXReader reader = new SAXReader(); //通过read方法读取一个文件,转换成Document对象 Document document = null; try { document = reader.read(file); } catch (DocumentException e) { throw new RuntimeException("load mapper info error:", e); } //获取根节点元素对象<mapper> Element root = document.getRootElement(); //获取命名空间 String namespace = root.attribute("namespace").getData().toString(); //获取select子节点列表 List<Element> selects = root.elements("select"); //遍历select节点,将信息记录到MappedStatement对象,并登记到configuration对象中 for (Element element : selects) { MappedStatement mappedStatement = new MappedStatement();//实例化mappedStatement String id = element.attribute("id").getData().toString();//读取id属性 String resultType = element.attribute("resultType").getData().toString();//读取resultType属性 String sql = element.getData().toString();//读取SQL语句信息 String sourceId = namespace + "." + id; //给mappedStatement属性赋值 mappedStatement.setSourceId(sourceId); mappedStatement.setResultType(resultType); mappedStatement.setSql(sql); mappedStatement.setNamespace(namespace); //注册到configuration对象中 configuration.getMappedStatments().put(sourceId, mappedStatement); } } }
4.SqlSession相关类
SqlSession.java接口:
package com.taoxi.mybatis.mysimple.session; import java.util.List; public interface SqlSession { public <T> T selectOne(String statement, Object parameter); public <E> List<E> selectList(String statement, Object parameter); public <T> T getMapper(Class<T> type); }
DefaultSqlSession.java默认实现类:
package com.taoxi.mybatis.mysimple.session; import com.taoxi.mybatis.mysimple.binding.MapperProxy; import com.taoxi.mybatis.mysimple.config.Configuration; import com.taoxi.mybatis.mysimple.config.MappedStatement; import com.taoxi.mybatis.mysimple.executor.DefaultExecutor; import com.taoxi.mybatis.mysimple.executor.Executor; import java.lang.reflect.Proxy; import java.util.List; public class DefaultSqlSession implements SqlSession{ private final Configuration configuration; private Executor executor; public DefaultSqlSession(Configuration configuration) { super(); this.configuration = configuration; executor = new DefaultExecutor(configuration); } public <T> T selectOne(String statement, Object parameter) { //执行Sql语句获取查询结果 List<Object> selectList = this.selectList(statement, parameter); //查询结果为空返回null if (selectList == null || selectList.size()==0) { return null; } if (selectList.size()==1) { return (T)selectList.get(0); }else{ throw new RuntimeException("Too Many Results!"); } } public <E> List<E> selectList(String statement, Object parameter) { try { MappedStatement ms = configuration.getMappedStatments().get(statement); return executor.query(ms, parameter); } catch (Exception e) { throw new RuntimeException("select list error:", e); } } public <T> T getMapper(Class<T> type) { MapperProxy mapperProxy = new MapperProxy(this); return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mapperProxy); } }
MapperProxy.java代理处理类:
package com.taoxi.mybatis.mysimple.binding; import com.taoxi.mybatis.mysimple.session.SqlSession; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Collection; public class MapperProxy implements InvocationHandler { private SqlSession session; public MapperProxy(SqlSession session) { super(); this.session = session; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Collection.class.isAssignableFrom(method.getReturnType())){ return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(), args==null?null:args[0]); }else { return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(), args==null?null:args[0]); } } }
5.Executor执行器相关类
Executor.java执行器接口:
package com.taoxi.mybatis.mysimple.executor; import com.taoxi.mybatis.mysimple.config.MappedStatement; import java.util.List; public interface Executor { public <E> List<E> query(MappedStatement ms, Object parameter) throws Exception; }
DefaultExecutor.java执行器默认实现类:
package com.taoxi.mybatis.mysimple.executor; import com.taoxi.mybatis.mysimple.config.Configuration; import com.taoxi.mybatis.mysimple.config.MappedStatement; import com.taoxi.mybatis.mysimple.util.ReflectionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.*; import java.util.ArrayList; import java.util.List; public class DefaultExecutor implements Executor{ private static Logger logger = LoggerFactory.getLogger(DefaultExecutor.class); private final Configuration configuration; public DefaultExecutor(Configuration configuration) { super(); this.configuration = configuration; } public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException{ ArrayList<E> ret = new ArrayList<E>();//定义返回结果集 try { Class.forName(configuration.getJdbcDriver());//加载驱动程序 } catch (ClassNotFoundException e) { e.printStackTrace(); } Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //获取连接,从Configuration中获取数据库信息 connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUsername(), configuration.getJdbcPassword()); //创建prepareStatement,从MappedStatement中获取sql语句 preparedStatement = connection.prepareStatement(ms.getSql()); //处理sql语句中的占位符 parameterize(preparedStatement, parameter); //执行查询操作获取resultSet resultSet = preparedStatement.executeQuery(); //将结果集通过反射技术,填充到list中 handlerResultSet(resultSet, ret, ms.getResultType()); } catch (SQLException e) { throw e; }finally { try { resultSet.close(); preparedStatement.close(); connection.close(); } catch (SQLException e) { throw e; } } return ret; } //对prepareStatement中的占位符进行处理 private void parameterize(PreparedStatement preparedStatement, Object parameter) throws SQLException { if (parameter instanceof Integer) { preparedStatement.setInt(1,((Integer) parameter).intValue()); } else if (parameter instanceof Long) { preparedStatement.setLong(1, ((Long) parameter).longValue()); } else if (parameter instanceof String) { preparedStatement.setString(1, (String)parameter); } } //读取resultset中的数据,并转换成目标对象 private <E> void handlerResultSet(ResultSet resultSet, List<E> ret, String className) { Class<E> clazz = null; try { //通过反射获取类对象 clazz = (Class<E>)Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { while (resultSet.next()) { Object entity = clazz.newInstance(); //使用反射工具将resultSet中的数据填充到entity中 ReflectionUtil.setPropToBeanFromResult(entity, resultSet); //对象加入返回集合中 ret.add((E)entity); } } catch (Exception e) { e.printStackTrace(); } } }
ReflectionUti.java反射工具类:
package com.taoxi.mybatis.mysimple.util; import java.lang.reflect.Field; import java.sql.ResultSet; public class ReflectionUtil { public static void setPropToBean(Object bean, String propName, Object value) { Field field; try { field = field = bean.getClass().getDeclaredField(propName); field.setAccessible(true); field.set(bean, value); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } public static void setPropToBeanFromResult(Object entity, ResultSet resultSet) throws Exception { Field[] declaredFields = entity.getClass().getDeclaredFields(); for (int i = 0;i<declaredFields.length;i++) { if (declaredFields[i].getType().getSimpleName().equals("String")){ setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName())); } else if (declaredFields[i].getType().getSimpleName().equals("Integer")){ setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName())); } else if (declaredFields[i].getType().getSimpleName().equals("Long")){ setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName())); } } } public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Class<?> clazz = Class.forName("com.taoxi.mybatis.mysimple.entity.Dept"); Object dept = clazz.newInstance(); ReflectionUtil.setPropToBean(dept, "dName", "zhongguo"); System.out.println(dept); } }
源码实现工程地址:https://github.com/taoxibj/study/tree/master/mysimple
DeptMapper
应对软件变化