傳統的 JDBC 編程查詢數據庫的代碼和過程總結。java
public static void main(String[] args) { //聲明Connection對象 Connection con = null; //遍歷查詢結果集 try { //加載驅動程序 Class.forName("com.mysql.jdbc.Driver"); //建立 connection 對象 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","username","password"); //使用 connection 對象建立statement 或者 PreparedStatement 類對象,用來執行SQL語句 Statement statement = con.createStatement(); //要執行的SQL語句 String sql = "select * from emp"; //3.ResultSet類,用來存放獲取的結果集!! ResultSet rs = statement.executeQuery(sql); String job = ""; String id = ""; while(rs.next()){ //獲取stuname這列數據 job = rs.getString("job"); //獲取stuid這列數據 id = rs.getString("ename"); //輸出結果 System.out.println(id + "\t" + job); } } catch(ClassNotFoundException e) { e.printStackTrace(); } catch(SQLException e) { //數據庫鏈接失敗異常處理 e.printStackTrace(); }catch (Exception e) { e.printStackTrace(); }finally{ rs.close(); con.close(); } }
編碼方式實現 MyBatis 查詢數據庫,方便你們理解,不使用 SpringMybatis,加入 Spring 後總體流程會複雜不少。使用 MyBatis 後能將原來的傳統的 JDBC 編程編的如此簡單。具體流程總結。mysql
//獲取 sqlSession,sqlSession 至關於傳統 JDBC 的 Conection public static SqlSession getSqlSession(){ InputStream configFile = new FileInputStream(filePath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile); return sqlSessionFactory.openSession(); } //使用 sqlSession 得到對應的 mapper,mapper 用來執行 sql 語句。 public static User get(SqlSession sqlSession, int id){ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); return userMapper.selectByPrimaryKey(id); }
總結git
下面來具體分析 MyBatis 代碼的執行過程**github
總體架構sql
源碼分析數據庫
先說一下大部分框架的代碼流程:apache
再看咱們的配置文件。編程
<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </transactionManager> </environment> </environments> </configuration> <mappers> <mapper resource="xml/UserMapper.xml"/> </mappers>
public static SqlSession getSqlSession(){ //讀取上面的配置文件 InputStream configFile = new FileInputStream(filePath); //根據上面配置的 dataSource 配置 SqlSessionFactory,而且創建 Mapper 接口和 xml 之間的關係。 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile); //工廠方法返回一個 sqlSession return sqlSessionFactory.openSession(); } //咱們來重點看看 openSession 作了什麼操做, DefaultSqlSessionFactory.java @Override public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); } public Configuration getConfiguration() { return this.configuration; } //這個函數裏面有着事務控制相關的代碼。 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); //根據上面的參數獲得 TransactionFactory,經過 TransactionFactory 生成一個 Transaction,能夠理解爲這個 SqlSession 的事務控制器 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 將這個事務控制器封裝在 Executor 裏 Executor executor = this.configuration.newExecutor(tx, execType); // 使用 configuration 配置類,Executor,和 configuration(是否自動提交) 來構建一個 DefaultSqlSession。 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
SqlSession 的實現流程。緩存
SqlSession 的接口定義:裏面定義了增刪改查和提交回滾等方法。session
public interface SqlSession extends Closeable { <T> T selectOne(String var1); <T> T selectOne(String var1, Object var2); <E> List<E> selectList(String var1); <E> List<E> selectList(String var1, Object var2); <E> List<E> selectList(String var1, Object var2, RowBounds var3); <K, V> Map<K, V> selectMap(String var1, String var2); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4); <T> Cursor<T> selectCursor(String var1); <T> Cursor<T> selectCursor(String var1, Object var2); <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3); void select(String var1, Object var2, ResultHandler var3); void select(String var1, ResultHandler var2); void select(String var1, Object var2, RowBounds var3, ResultHandler var4); int insert(String var1); int insert(String var1, Object var2); int update(String var1); int update(String var1, Object var2); int delete(String var1); int delete(String var1, Object var2); void commit(); void commit(boolean var1); void rollback(); void rollback(boolean var1); List<BatchResult> flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> var1); Connection getConnection(); }
接下來用 sqlSession 獲取對應的 Mapper:
//使用 sqlSession 得到對應的 mapper,mapper 用來執行 sql 語句。 public static User get(SqlSession sqlSession, int id){ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); return userMapper.selectByPrimaryKey(id); }
DefaultSqlSession 的 getMapper 實現:
public <T> T getMapper(Class<T> type) { return this.configuration.getMapper(type, this); } //從 configuration 裏面 getMapper,Mapper 就在 Configuration 裏 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
MapperRegistry 裏 getMapper 的最終實現:
這裏就要說明一下,咱們的接口裏面只定義了抽象的增刪改查,而這個接口並無任何實現類,那麼這個 xml 究竟是如何與接口關聯起來並生成實現類那?
public class MapperRegistry { private final Configuration config; // 用一個 Map 來存儲接口和 xml 文件之間的映射關係,key 應該是接口,可是 value 是 MapperProxyFactory private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap(); public MapperRegistry(Configuration config) { this.config = config; } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //獲取到這個接口對應的 MapperProxyFactory。 MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { //用上一步獲取的 MapperProxyFactory 和 sqlSession 構建對應的 Class return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } } }
接下來咱們看看 newInstance 的具體實現:
public T newInstance(SqlSession sqlSession) { // mapperInterface 就是接口 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { //動態代理,這裏的動態代理有一些不同 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }
正常流程的動態代理:
與傳統的動態代理相比,MyBatis 的接口是沒有實現類的,那麼它又是怎麼實現動態代理的那?
咱們來看一下 MapperProxy 的源碼:
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } // 正常的動態代理中 Object proxy 這個參數應該是接口的實現類 // com.paul.pkg.UserMapper@5a123uf // 如今裏面是 org.apache.ibatis.binding.MapperProxy@6y213kn, 這倆面 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } // Mapper 走這個流程,先嚐試在緩存裏獲取 method MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { // mapperMethod 的構建,經過接口名,方法,和 xml 配置(經過 sqlSession 的 Configuration 得到) mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); //經過 execute 執行方法,由於 sqlSession 封裝了 Executor,因此還要傳進來,execute 方法使用 //sqlSession 裏面的方法。 this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
來看 MapperMethod 的定義:
// command 裏面包含了方法名,好比 com.paul.pkg.selectByPrimaryKey // type, 表示是 SELECT,UPDATE,INSERT,或者 DELETE // method 是方法的簽名 public class MapperMethod { private final MapperMethod.SqlCommand command; private final MapperMethod.MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, mapperInterface, method); } }
進入 DefaultSqlSession 執行對應的 sql 語句:
public <T> T selectOne(String statement, Object parameter) { List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { // 這裏又須要 configuration 來獲取對應的 statement // MappedStatement 裏面有 xml 文件,和要執行的方法,就是 xml 裏面的 id,statementType,以及 sql 語句。 MappedStatement ms = this.configuration.getMappedStatement(statement); // 用 executor 執行 query,executor 裏面應該是包裝了 JDBC。 var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }
Executor 的實現類裏面執行 query 方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } // 使用 delegate 去 query,delegate 是 SimpleExecutor。裏面使用 JDBC 進行數據庫操做。 return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
項目總體使用 Maven 構建,mybatis-demo 是脫離 Spring 的 MyBatis 使用的例子。paul-mybatis 是咱們本身實現的 mybatis 框架。
首先按照咱們之前的使用 mybatis 代碼時的流程,建立 mapper 接口,xml 文件,和 POJO以及集一些配置文件。
接口:TUserMapper
package com.paul.mybatis.mapper; import com.paul.mybatis.entity.TUser; import java.util.List; public interface TUserMapper { TUser selectByPrimaryKey(Integer id); List<TUser> selectAll(); }
xml 文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.paul.mybatis.mapper.TUserMapper"> <select id="selectByPrimaryKey" resultType="TUser"> select * from t_user where id = #{id,jdbcType=INTEGER} </select> <select id="selectAll" resultType="TUser"> select * from t_user </select> </mapper>
實體類,屬性應該與數據庫想匹配
package com.paul.mybatis.entity; public class TUser { private Integer id; private String userName; private String realName; private Byte sex; private String mobile; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } public Byte getSex() { return sex; } public void setSex(Byte sex) { this.sex = sex; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } }
數據庫鏈接配置文件,db.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root
下面咱們來關注 xml 文件,mapper 文件裏的 namespace,id,resultType 和 sql 語句都要存儲起來,咱們定義一個 POJO 來存儲這些信息。
package com.paul.mybatis.confiuration; /** * * XML 中的 sql 配置信息加載到這個類中 * */ public class MappedStatement { private String namespace; private String id; private String resultType; private String sql; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getId() { return id; } public void setId(String id) { this.id = id; } 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; } }
下面來建立一個 Configuration 類,用來保存全部配置文件和 xml 文件裏的信息。
package com.paul.mybatis.confiuration; import java.util.HashMap; import java.util.List; import java.util.Map; /** * * 全部的配置信息 * */ public class Configuration { private String jdbcDriver; private String jdbcUrl; private String jdbcPassword; private String jdbcUsername; private Map<String,MappedStatement> mappedStatement = new HashMap<>(); public Map<String, MappedStatement> getMappedStatement() { return mappedStatement; } public void setMappedStatement(Map<String, MappedStatement> mappedStatement) { this.mappedStatement = 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 getJdbcPassword() { return jdbcPassword; } public void setJdbcPassword(String jdbcPassword) { this.jdbcPassword = jdbcPassword; } public String getJdbcUsername() { return jdbcUsername; } public void setJdbcUsername(String jdbcUsername) { this.jdbcUsername = jdbcUsername; } }
有了配置類以後,咱們能夠經過這個配置類構建一個 SqlSessionFactory 了。
SqlSessionFactory 抽象模版
package com.paul.mybatis.factory; import com.paul.mybatis.sqlsession.SqlSession; public interface SqlSessionFactory { SqlSession openSession(); }
Default 實現類主要完成了兩個功能,加載配置信息到 Configuration 對象裏,實現建立 SqlSession 的功能。
package com.paul.mybatis.factory; import com.paul.mybatis.confiuration.Configuration; import com.paul.mybatis.confiuration.MappedStatement; import com.paul.mybatis.sqlsession.DefaultSqlSession; import com.paul.mybatis.sqlsession.SqlSession; 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.ArrayList; import java.util.List; import java.util.Properties; /** * * 1.初始化時就完成了 configuration 的實例化 * 2.工廠類,生成 sqlSession * */ public class DefaultSqlSessionFactory implements SqlSessionFactory{ //但願Configuration 是單例子而且惟一的 private final Configuration configuration = new Configuration(); // xml 文件存放的位置 private static final String MAPPER_CONFIG_LOCATION = "mappers"; // 數據庫信息存放的位置 private static final String DB_CONFIG_FILE = "db.properties"; public DefaultSqlSessionFactory() { loadDBInfo(); loadMapperInfo(); } private void loadDBInfo() { InputStream db = this.getClass().getClassLoader().getResourceAsStream(DB_CONFIG_FILE); Properties p = new Properties(); try { p.load(db); } catch (IOException e) { e.printStackTrace(); } //將配置信息寫入Configuration 對象 configuration.setJdbcDriver(p.get("jdbc.driver").toString()); configuration.setJdbcUrl(p.get("jdbc.url").toString()); configuration.setJdbcUsername(p.get("jdbc.username").toString()); configuration.setJdbcPassword(p.get("jdbc.password").toString()); } //解析並加載xml文件 private void loadMapperInfo(){ URL resources = null; resources = this.getClass().getClassLoader().getResource(MAPPER_CONFIG_LOCATION); File mappers = new File(resources.getFile()); //讀取文件夾下面的文件信息 if(mappers.isDirectory()){ File[] files = mappers.listFiles(); for(File file:files){ loadMapperInfo(file); } } } private void loadMapperInfo(File file){ SAXReader reader = new SAXReader(); //經過read方法讀取一個文件轉換成Document 對象 Document document = null; try { document = reader.read(file); } catch (DocumentException e) { e.printStackTrace(); } //獲取根結點元素對象<mapper> Element e = document.getRootElement(); //獲取命名空間namespace String namespace = e.attribute("namespace").getData().toString(); //獲取select,insert,update,delete子節點列表 List<Element> selects = e.elements("select"); List<Element> inserts = e.elements("select"); List<Element> updates = e.elements("select"); List<Element> deletes = e.elements("select"); List<Element> all = new ArrayList<>(); all.addAll(selects); all.addAll(inserts); all.addAll(updates); all.addAll(deletes); //遍歷節點,組裝成 MappedStatement 而後放入到configuration 對象中 for(Element ele:all){ MappedStatement mappedStatement = new MappedStatement(); String id = ele.attribute("id").getData().toString(); String resultType = ele.attribute("resultType").getData().toString(); String sql = ele.getData().toString(); mappedStatement.setId(namespace+"."+id); mappedStatement.setResultType(resultType); mappedStatement.setNamespace(namespace); mappedStatement.setSql(sql); configuration.getMappedStatement().put(namespace+"."+id,mappedStatement); } } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); } }
在 SqlSessionFactory 裏建立了 DefaultSqlSession,咱們看看它的具體實現。SqlSession裏面應該封裝了全部數據庫的具體操做和一些獲取 mapper 實現類的方法。使用動態代理生成一個增強類。這裏面最終仍是把數據庫的相關操做轉給 SqlSession,使用 mapper 能使編程更加優雅。
SqlSession 接口,定義模版方法
package com.paul.mybatis.sqlsession; import java.util.List; /** * * 封裝了全部數據庫的操做 * 全部功能都是基於 Excutor 來實現的,Executor 封裝了 JDBC 操做 * * */ public interface SqlSession { /** * 根據傳入的條件查詢單一結果 * @param statement 方法對應 sql 語句,namespace+id * @param parameter 要傳入 sql 語句中的查詢參數 * @param <T> 返回指定的結果對象 * @return */ <T> T selectOne(String statement, Object parameter); <T> List<T> selectList(String statement, Object parameter); <T> T getMapper(Class<T> type); }
Default 的 SqlSession 實現類。裏面須要傳入 Executor,這個 Executor 裏面封裝了 JDBC 操做數據庫的流程。咱們重點關注 getMapper 方法。
package com.paul.mybatis.sqlsession; import com.paul.mybatis.bind.MapperProxy; import com.paul.mybatis.confiuration.Configuration; import com.paul.mybatis.confiuration.MappedStatement; import com.paul.mybatis.executor.Executor; import com.paul.mybatis.executor.SimpleExecutor; 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 SimpleExecutor(configuration); } @Override public <T> T selectOne(String statement, Object parameter) { List<T> 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"); } } @Override public <T> List<T> selectList(String statement, Object parameter) { MappedStatement ms = configuration.getMappedStatement().get(statement); return executor.query(ms,parameter); } @Override public <T> T getMapper(Class<T> type) { MapperProxy mp = new MapperProxy(this); //給我一個接口,還你一個實現類 return (T)Proxy.newProxyInstance(type.getClassLoader(),new Class[]{type},mp); } }
動態代理的 InvocationHandler。
package com.paul.mybatis.bind; import com.paul.mybatis.sqlsession.SqlSession; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; /** * * 將請求轉發給 sqlSession * */ public class MapperProxy implements InvocationHandler { private SqlSession sqlSession; public MapperProxy(SqlSession sqlSession) { this.sqlSession = sqlSession; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getDeclaringClass().getName()+"."+method.getName()); if(Collection.class.isAssignableFrom(method.getReturnType())){ return sqlSession.selectList(method.getDeclaringClass().getName()+"."+method.getName(),args==null?null:args[0]); }else{ return sqlSession.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),args==null?null:args[0]); } } }
最後來看咱們的測試類
package com.paul.mybatis; import com.paul.mybatis.entity.TUser; import com.paul.mybatis.factory.DefaultSqlSessionFactory; import com.paul.mybatis.factory.SqlSessionFactory; import com.paul.mybatis.mapper.TUserMapper; import com.paul.mybatis.sqlsession.SqlSession; public class TestDemo { public static void main(String[] args) { SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(); SqlSession sqlSession = sqlSessionFactory.openSession(); TUserMapper mapper = sqlSession.getMapper(TUserMapper.class); TUser user = mapper.selectByPrimaryKey(1); System.out.println(user.toString()); } }
整個項目的源碼在項目源碼,但願你們 mark 一下,一塊兒改進。