上节讲到快速入门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动态代理.
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执行的。
结果集 Result 再通过反射机制映射到对象上面,便做好了数据的映射(关于映射具体内容可查阅资料及源码),到这我们已经完成了一个简易的Mybatis框架了.
通过手写一个简单的Mybatis框架,我们就可以看得懂源码了,学习框架设计的思路并且增强我们Java的内功.