mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本
什么是 MyBatis ?
MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。
MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。(這是官網解釋)
MyBatis 運行原理
當框架啟動時,通過configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可以使用xml方式或者注解方式,然后由configuration獲得sqlsessionfactory對象,再由sqlsessionfactory獲得sqlsession數據庫訪問會話對象,通過會話對象獲得對應DAO層的mapper對象,通過調用mapper對象相應方法,框架就會自動執行SQL語句從而獲得結果。
手寫 mybatis
其實整體流程就是這么簡單,我們來一起實現一個簡單版本的 mybatis。
創作目的
(1)深入學習 mybatis 的原理
一千個讀者就有一千個哈姆雷特,一千個作者就有一千個莎士比亞。——老馬
(2)實現屬于自己的 mybatis 工具。
數據庫的種類實際上有幾百種,比如工作中就用到過 GreenPlum 這種相對小眾的數據庫,這時候 mybatis 可能就不能使用了。
感覺大可不必,符合 SQL 標準都應該統一支持下,這樣更加方便實用。
實現方式
本系列目前共計 17 個迭代版本,基本完成了 mybatis 的核心特性。
耗時大概十天左右,相對實現的方式比較簡單。
采用 mvp 的開發策略,逐漸添加新的特性。
本系列將對核心代碼進行講解,完整代碼已經全部開源
https://github.com/houbb/mybatis快速體驗
mysql 安裝
不是本系列重點,請自行找資料。
版本:使用的是 v5.7 版本,v8.0 之后依賴的驅動包會有所不同。
sql 執行
-- auto-generated definition create table user (id int auto_incrementprimary key,name varchar(100) not null,password varchar(100) not null );insert into user (name, password) value ('ryo', '123456');maven 引入
<dependency><groupId>com.github.houbb</groupId><artifactId>mybatis</artifactId><version>0.0.1</version> </dependency>配置文件
- mybatis-config-5-7.xml
測試代碼
Config config = new XmlConfig("mybatis-config-5-7.xml");SqlSession sqlSession = new DefaultSessionFactory(config).openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectById(1L); System.out.println(user);輸出結果:
User{id=1, name='ryo', password='123456'}是不是有種 mybatis 初戀般的感覺呢?
到這里都是引子,下面我們來講述下一些核心實現。
代碼實現
maven 依賴
這里我們需要訪問 mysql,也需要解析 xml。
需要引入如下的依賴:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.29</version> </dependency> <dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version> </dependency>接口定義
上述的測試代碼中,我們演示用到的幾個核心接口如下:
- Config.java
配置接口
/*** 配置信息* @author binbin.hou* @since 0.0.1*/ public interface Config {/*** 獲取數據源信息* @return 數據源配置* @since 0.0.1*/DataSource getDataSource();/*** 獲取映射類信息* @param clazz 類信息* @return 結果* @since 0.0.1*/MapperClass getMapperData(final Class clazz);/*** 獲取映射類信息* @param clazz 類信息* @param methodName 方法名稱* @return 結果* @since 0.0.1*/MapperMethod getMapperMethod(final Class clazz,final String methodName);/*** 數據庫連接信息* @return 連接信息* @since 0.0.1*/Connection getConnection(); }- SqlSession.java
- UserMapper.java
UserMapper 就是我們經常定義的 mapper
public interface UserMapper {User selectById(final long id);}下面我們來看看對應的幾個比較重要的實現。
xml 的配置初始化
我們的很多配置放在 config.xml 文件中,肯定是通過解析 xml 實現的。
基礎屬性
public class XmlConfig extends ConfigAdaptor {/*** 文件配置路徑** @since 0.0.1*/private final String configPath;/*** 配置文件信息** @since 0.0.1*/private Element root;/*** 數據源信息** @since 0.0.1*/private DataSource dataSource;/*** mapper 注冊類** @since 0.0.1*/private final MapperRegister mapperRegister = new MapperRegister();public XmlConfig(String configPath) {this.configPath = configPath;// 配置初始化initProperties();// 初始化數據連接信息initDataSource();// mapper 信息initMapper();}@Overridepublic DataSource getDataSource() {return this.dataSource;}@Overridepublic Connection getConnection() {try {Class.forName(dataSource.driver());return DriverManager.getConnection(dataSource.url(), dataSource.username(), dataSource.password());} catch (ClassNotFoundException | SQLException e) {throw new MybatisException(e);}}@Overridepublic MapperMethod getMapperMethod(Class clazz, String methodName) {return this.mapperRegister.getMapperMethod(clazz, methodName);} }配置初始化
這里就是解析 xml 文件的 root 節點,便于后續使用:
root 節點的初始化如下:
/*** 獲取根節點* @param path 配置路徑* @return 元素* @since 0.0.1*/ public static Element getRoot(final String path) {try {// 初始化數據庫連接信息InputStream inputStream = StreamUtil.getInputStream(path);SAXReader reader = new SAXReader();Document document = reader.read(inputStream);return document.getRootElement();} catch (DocumentException e) {throw new MybatisException(e);} }初始化數據連接信息
這就是解析 xml 中對于 dataSource 的配置信息:
/*** 初始化數據源** @since 0.0.1*/ private void initDataSource() {// 根據配置初始化連接信息this.dataSource = new DataSource();Element dsElem = root.element("dataSource");Map<String, String> map = new HashMap<>(4);for (Object property : dsElem.elements("property")) {Element element = (Element) property;String name = element.attributeValue("name");String value = element.attributeValue("value");map.put("jdbc." + name, value);}dataSource.username(map.get(DataSourceConst.USERNAME)).password(map.get(DataSourceConst.PASSWORD)).driver(map.get(DataSourceConst.DRIVER)).url(map.get(DataSourceConst.URL)); }初始化 mapper
解析 xml 中的 mapper 配置。
/*** 初始化 mapper 信息** @since 0.0.1*/ private void initMapper() {Element mappers = root.element("mappers");// 遍歷所有需要初始化的 mapper 文件路徑for (Object item : mappers.elements("mapper")) {Element mapper = (Element) item;String path = mapper.attributeValue("resource");mapperRegister.addMapper(path);} }mapperRegister 就是對方法的元數據進行一些構建,比如出參,入參的類型,等等,便于后期使用。
比如我們的 UserMapper.xml 方法內容如下:
<select id = "selectById" paramType="java.lang.Long" resultType = "com.github.houbb.mybatis.domain.User">select * from user where id = ? </select>sql 就是:select * from user where id = ?
方法標識:selectById
入參:Long
出參:User
創建 session
如何創建
SqlSession sqlSession = new DefaultSessionFactory(config).openSession();這句話實際執行的是:
@Override public SqlSession openSession() {return new DefaultSqlSession(config, new SimpleExecutor()); }獲取 mapper 實現
UserMapper userMapper = sqlSession.getMapper(UserMapper.class)這里獲取 mapper,實際獲取的是什么呢?
實際上獲取到的是一個代理。
mybatis 將我們的接口,和實際 xml 中的 sql 二者通過動態代理結合,讓我們調用 xml 中的 sql 和使用接口方法一樣自然。
獲取代理
getMapper 實際上是一個動態代理。
@Override @SuppressWarnings("all") public <T> T getMapper(Class<T> clazz) {MapperProxy proxy = new MapperProxy(clazz, this);return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, proxy); }動態代理的實現
MapperProxy 的實現如下:
public class MapperProxy implements InvocationHandler {/*** 類信息** @since 0.0.1*/private final Class clazz;/*** sql session** @since 0.0.1*/private final SqlSession sqlSession;public MapperProxy(Class clazz, SqlSession sqlSession) {this.clazz = clazz;this.sqlSession = sqlSession;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MapperMethod mapperMethod = this.sqlSession.getConfig().getMapperMethod(clazz, method.getName());if (mapperMethod != null) {return this.sqlSession.selectOne(mapperMethod, args);}return method.invoke(proxy, args);}}代理了什么?
當我們執行 userMapper.selectById(1L) 時,實際執行的是什么?
實際執行的是 sqlSession.selectOne(mapperMethod, args)
代理實現
selectOne 是比較核心的內容了。
整體實現
整體如下
public <T> T query(final Config config,MapperMethod method, Object[] args) {try(Connection connection = config.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(method.getSql());) {// 2. 處理參數parameterHandle(preparedStatement, args);// 3. 執行方法preparedStatement.execute();// 4. 處理結果final Class resultType = method.getResultType();ResultSet resultSet = preparedStatement.getResultSet();ResultHandler resultHandler = new ResultHandler(resultType);Object result = resultHandler.buildResult(resultSet);return (T) result;} catch (SQLException ex) {throw new MybatisException(ex);} }我們獲取到 xml 中的 sql,然后構建 jdbc 中大家比較熟悉的 PreparedStatement。
然后對出參和入參進行處理,最后返回結果。
入參設置
public void setParams(final Object[] objects) {try {for(int i = 0; i < objects.length; i++) {Object value = objects[i];// 目標類型,這個后期可以根據 jdbcType 獲取// jdbc 下標從1開始statement.setObject(i+1, value);}} catch (SQLException throwables) {throw new MybatisException(throwables);} }針對我們非常簡單的例子:
select * from user where id = ?那就是直接把入參中的 1L 設置到占位符 ? 即可。
出參處理
這里主要用到反射,將查詢結果和 javaBean 做一一映射。
/*** 構建結果* @param resultSet 結果集合* @return 結果* @since 0.0.1*/ public Object buildResult(final ResultSet resultSet) {try {// 基本類型,非 java 對象,直接返回即可。// 可以進行抽象Object instance = resultType.newInstance();// 結果大小的判斷// 為空直接返回,大于1則報錯if(resultSet.next()) {List<Field> fieldList = ClassUtil.getAllFieldList(resultType);for(Field field : fieldList) {Object value = getResult(field, resultSet);ReflectFieldUtil.setValue(field, instance, value);}// 返回設置值后的結果return instance;}return null;} catch (InstantiationException | IllegalAccessException | SQLException e) {throw new MybatisException(e);} }到這里,一個簡易版的 myabtis 就可以跑起來了。
當然這里還有很多的不足之處,我們后續都會一一優化。
完整代碼地址
為了便于學習,完整版本代碼以開源:
https://github.com/houbb/mybatishttp://weixin.qq.com/r/GSnk-PfEiar2rbOf93wL<br> (二維碼自動識別)
總結
以上是生活随笔為你收集整理的mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android tcp socket框架
- 下一篇: 待办事项桌面插件_求一款安卓手机上可添加