简化版 Mybatis 实现思路
- 1. 创建 SqlSessionFactory 实例.
- 2. 实例化过程中,加载配置文件创建 configuration 对象.
- 3. 通过 factory 创建 SqlSession 对象, 把 configuaration 传入 SqlSession.
- 4. 通过 SqlSession 获取 mapper 接口动态代理
- 5. 通过代理对调 sqlsession 中查询方法;
- 6.sqlsession 将查询方法转发给 executor;
- 7.executor 基于 JDBC 访问数据库获取数据;
- 8.executor 通过反射将数据转换成 POJO 并返回给 sqlsession;
- 9. 数据返回给调用者
上节讲到快速入门 mybatis 的 demo 三大阶段
// 1. 读取 mybatis 配置文件创 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
//------------- 第二阶段 -------------
// 2. 获取 sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取对应 mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
//------------- 第三阶段 -------------
// 4. 执行查询语句并返回结果
TUser user = mapper.selectByPrimaryKey(1);
System.out.println(user.toString());
第一阶段:
第一阶段先把配置文件加载到内存,包括数据库信息和 mapper.xml。
针对 mapper.xml 我们定义一个 MappedStatement 类来存入相应信息.
public class MappedStatement {
// 此处忽略 getset 方法
private String namespace;
private String sourceId;//mapper 接口路径 +xml 里面的每一个 id
private String sql;//sql 语句
private String resultType;// 返回类型
}
再定义一个全局配置信息即 Configuration 存放所有配置信息:
public class Configuration {
// 记录 mapper xml 文件存放的位置
public static final String MAPPER_CONFIG_LOCATION = "config";
// 记录数据库连接信息文件存放位置
public static final String DB_CONFIG_FILE = "db.properties";
private String dbUrl;
private String dbUserName;
private String dbPassword;
private String dbDriver;
//mapper xml 解析完以后 select 节点的信息存放在 mappedStatements,key 为 MappedStatement 里面
// 的 sourceId
protected final Map<String, MappedStatement> mappedStatements = new HashMap<String, MappedStatement>();
// 为 mapper 接口生成动态代理的方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return MapperProxyFactory.getMapperProxy(sqlSession, type);
}
}
SqlSessionFactory 实例化,并加载 configuaration 对象信息, 这样就把所有的配置信息加载到内存里
public class SqlSessionFactory {
// 配置对象全局唯一 加载数据库信息和 mapper 文件信息
private Configuration conf = new Configuration();
public SqlSessionFactory() {
// 加载数据库信息
loadDbInfo();
// 加载 mapper 文件信息
loadMappersInfo();}
private void loadMappersInfo() {
URL resources =null;
resources = SqlSessionFactory.class.getClassLoader().getResource(conf.MAPPER_CONFIG_LOCATION);
File mappers = new File(resources.getFile());
if(mappers.isDirectory()){File[] listFiles = mappers.listFiles();
for (File file : listFiles) {loadMapperInfo(file);
}
}
}
private void loadMapperInfo(File file) {
// 创建 saxReader 对象
SAXReader reader = new SAXReader();
// 通过 read 方法读取一个文件 转换成 Document 对象
Document document=null;
try {document = reader.read(file);
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
// 获取根节点元素对象
Element node = document.getRootElement();
// 获取命名空间
String namespace = node.attribute("namespace").getData().toString();
// 获取 select 子节点列表
List<Element> selects = node.elements("select");
for (Element element : selects) {// 遍历 select 节点,将信息记录到 MappedStatement 对象,并登记到 configuration 对象中
MappedStatement mappedStatement = new MappedStatement();
String id = element.attribute("id").getData().toString();
String resultType = element.attribute("resultType").getData().toString();
String sql = element.getData().toString();
String sourceId = namespace+"."+id;
mappedStatement.setSourceId(sourceId);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
mappedStatement.setNamespace(namespace);
conf.getMappedStatements().put(sourceId, mappedStatement);// 登记到 configuration 对象中
}
}
private void loadDbInfo() {InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(conf.DB_CONFIG_FILE);
Properties p = new Properties();
try {p.load(dbIn);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
conf.setDbDriver(p.get("jdbc.driver").toString());
conf.setDbPassword(p.get("jdbc.password").toString());
conf.setDbUrl(p.get("jdbc.url").toString());
conf.setDbUserName(p.get("jdbc.username").toString());
}
public SqlSession openSession(){SqlSession sqlSession = new DefaultSqlSession(conf);
return sqlSession;
}
}
第二阶段
第二阶段为获取 Sqlsession 并且从 sqlsession 获取 mapper 动态代理.
Sqlsession
- mybatis 暴露给外部的接口,实现增删改查的能力
- 1. 对外提供数据访问的 api
- 2. 对内将请求转发给 executor
-
3.executor 基于 JDBC 访问数据库
public class DefaultSqlSession implements SqlSession { // 配置对象全局唯一 加载数据库信息和 mapper 文件信息 private Configuration conf; // 真正提供数据库访问能力的对象 private Executor executor; public DefaultSqlSession(Configuration conf) {super(); this.conf = conf; executor = new SimpleExecutor(conf); } public <T> T selectOne(String statement, Object parameter) {List<Object> selectList = this.selectList(statement, parameter); if(selectList==null||selectList.size()==0){return null;} if(selectList.size()==1){return (T) selectList.get(0); }else {throw new RuntimeException("Too Many Result!"); } } public <E> List<E> selectList(String statement, Object parameter) {MappedStatement mappedStatement = conf.getMappedStatement(statement); try {return executor.query(mappedStatement, parameter); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace();} return null; } @Override // 获取当前 mapper 接口的动态代理 public <T> T getMapper(Class<T> type) {return conf.<T>getMapper(type, this); } }
Executor 是 Mybatis 核心接口定义了数据库操作的基本方法,Sqlsession 都是基于它来实现的
public interface Executor {<E> List<E> query(MappedStatement ms, Object parameter) throws SQLException; <T> T selectOne(String statement,Object parameter);
}
Executor 实现类:
public class SimpleExecutor implements Executor {
private Configuration conf;
public SimpleExecutor(Configuration conf) {this.conf = conf;}
public <E> List<E> query(MappedStatement ms, Object parameter)
throws SQLException {
// 获取 mappedStatement 对象,里面包含 sql 语句和目标对象等信息;MappedStatement mappedStatement = conf.getMappedStatement(ms.getSourceId());
//1. 获取 Connection 对象
Connection conn = getConnect();
//2. 实例化 StatementHandler 对象,准备实例化 Statement
StatementHandler statementHandler = new DefaultStatementHandler(mappedStatement);
//3. 通过 statementHandler 和 Connection 获取 PreparedStatement
PreparedStatement prepare = statementHandler.prepare(conn);
//4. 实例化 ParameterHandler 对象,对 Statement 中 sql 语句的占位符进行处理
ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
parameterHandler.setParameters(prepare);
//5. 执行查询语句,获取结果集 resultSet
ResultSet resultSet = statementHandler.query(prepare);
//6. 实例化 ResultSetHandler 对象,对 resultSet 中的结果集进行处理,转化成目标对象
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(mappedStatement);
return resultSetHandler.handleResultSets(resultSet);
}
@Override
public <T> T selectOne(String statement, Object parameter) {MappedStatement mappedStatement =conf.getMappedStatements().get(statement);
return null;
}
private Connection getConnect() {
Connection conn =null;
try {Class.forName(conf.getDbDriver());
conn = DriverManager.getConnection(conf.getDbUrl(), conf.getDbUserName(), conf.getDbPassword());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
return conn;
}
public Configuration getConf() {return conf;}
public void setConf(Configuration conf) {this.conf = conf;}
}
mapper 接口在我们工程里面没有实现类,是通过动态代理来执行方法的.
/**
* mapper 接口生成动态代理的工程类
*
*/
public class MapperProxyFactory<T> {public static <T> T getMapperProxy(SqlSession sqlSession,Class<T> mapperInterface){MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
}
InvocationHandler 实现类:
public class MapperProxy<T> implements InvocationHandler {
private SqlSession sqlSession;
private final Class<T> mapperInterface;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {super();
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
private <T> boolean isCollection(Class<T> type) {return Collection.class.isAssignableFrom(type);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {if (Object.class.equals(method.getDeclaringClass())) {// 如果是 Object 本身的方法不增强
return method.invoke(this, args);
}
Class<?> returnType = method.getReturnType();// 获取方法的返回参数 class 对象
Object ret = null;
if (isCollection(returnType)) {// 根据不同的返回参数类型调用不同的 sqlsession 不同的方法
ret = sqlSession.selectList(mapperInterface.getName()+"."+ method.getName(), args);
} else {ret = sqlSession.selectOne(mapperInterface.getName()+"."+ method.getName(), args);
}
return ret;
}
}
第三阶段
第三阶段执行查询并返回结果. 刚刚讲过我们执行数据库操作实际上是 executor 基于 jdbc 执行的。
jdbc 三大巨头,Connection,PreparedStatement,ResultSet,
结果集 Result 再通过反射机制映射到对象上面,便做好了数据的映射(关于映射具体内容可查阅资料及源码), 到这我们已经完成了一个简易的 Mybatis 框架了.
通过手写一个简单的 Mybatis 框架,我们就可以看得懂源码了,学习框架设计的思路并且增强我们 Java 的内功.