mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本
什么是 MyBatis ?
MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。
MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集。
MyBatis 可以使用簡(jiǎn)單的 XML 或注解來(lái)配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對(duì)象)映射成數(shù)據(jù)庫(kù)中的記錄。(這是官網(wǎng)解釋)
MyBatis 運(yùn)行原理
當(dāng)框架啟動(dòng)時(shí),通過(guò)configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可以使用xml方式或者注解方式,然后由configuration獲得sqlsessionfactory對(duì)象,再由sqlsessionfactory獲得sqlsession數(shù)據(jù)庫(kù)訪問(wèn)會(huì)話對(duì)象,通過(guò)會(huì)話對(duì)象獲得對(duì)應(yīng)DAO層的mapper對(duì)象,通過(guò)調(diào)用mapper對(duì)象相應(yīng)方法,框架就會(huì)自動(dòng)執(zhí)行SQL語(yǔ)句從而獲得結(jié)果。
手寫 mybatis
其實(shí)整體流程就是這么簡(jiǎn)單,我們來(lái)一起實(shí)現(xiàn)一個(gè)簡(jiǎn)單版本的 mybatis。
創(chuàng)作目的
(1)深入學(xué)習(xí) mybatis 的原理
一千個(gè)讀者就有一千個(gè)哈姆雷特,一千個(gè)作者就有一千個(gè)莎士比亞?!像R
(2)實(shí)現(xiàn)屬于自己的 mybatis 工具。
數(shù)據(jù)庫(kù)的種類實(shí)際上有幾百種,比如工作中就用到過(guò) GreenPlum 這種相對(duì)小眾的數(shù)據(jù)庫(kù),這時(shí)候 mybatis 可能就不能使用了。
感覺(jué)大可不必,符合 SQL 標(biāo)準(zhǔn)都應(yīng)該統(tǒng)一支持下,這樣更加方便實(shí)用。
實(shí)現(xiàn)方式
本系列目前共計(jì) 17 個(gè)迭代版本,基本完成了 mybatis 的核心特性。
耗時(shí)大概十天左右,相對(duì)實(shí)現(xiàn)的方式比較簡(jiǎn)單。
采用 mvp 的開(kāi)發(fā)策略,逐漸添加新的特性。
本系列將對(duì)核心代碼進(jìn)行講解,完整代碼已經(jīng)全部開(kāi)源
https://github.com/houbb/mybatis快速體驗(yàn)
mysql 安裝
不是本系列重點(diǎn),請(qǐng)自行找資料。
版本:使用的是 v5.7 版本,v8.0 之后依賴的驅(qū)動(dòng)包會(huì)有所不同。
sql 執(zhí)行
-- 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
測(cè)試代碼
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);輸出結(jié)果:
User{id=1, name='ryo', password='123456'}是不是有種 mybatis 初戀般的感覺(jué)呢?
到這里都是引子,下面我們來(lái)講述下一些核心實(shí)現(xiàn)。
代碼實(shí)現(xiàn)
maven 依賴
這里我們需要訪問(wèn) 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>接口定義
上述的測(cè)試代碼中,我們演示用到的幾個(gè)核心接口如下:
- Config.java
配置接口
/*** 配置信息* @author binbin.hou* @since 0.0.1*/ public interface Config {/*** 獲取數(shù)據(jù)源信息* @return 數(shù)據(jù)源配置* @since 0.0.1*/DataSource getDataSource();/*** 獲取映射類信息* @param clazz 類信息* @return 結(jié)果* @since 0.0.1*/MapperClass getMapperData(final Class clazz);/*** 獲取映射類信息* @param clazz 類信息* @param methodName 方法名稱* @return 結(jié)果* @since 0.0.1*/MapperMethod getMapperMethod(final Class clazz,final String methodName);/*** 數(shù)據(jù)庫(kù)連接信息* @return 連接信息* @since 0.0.1*/Connection getConnection(); }- SqlSession.java
- UserMapper.java
UserMapper 就是我們經(jīng)常定義的 mapper
public interface UserMapper {User selectById(final long id);}下面我們來(lái)看看對(duì)應(yīng)的幾個(gè)比較重要的實(shí)現(xiàn)。
xml 的配置初始化
我們的很多配置放在 config.xml 文件中,肯定是通過(guò)解析 xml 實(shí)現(xiàn)的。
基礎(chǔ)屬性
public class XmlConfig extends ConfigAdaptor {/*** 文件配置路徑** @since 0.0.1*/private final String configPath;/*** 配置文件信息** @since 0.0.1*/private Element root;/*** 數(shù)據(jù)源信息** @since 0.0.1*/private DataSource dataSource;/*** mapper 注冊(cè)類** @since 0.0.1*/private final MapperRegister mapperRegister = new MapperRegister();public XmlConfig(String configPath) {this.configPath = configPath;// 配置初始化initProperties();// 初始化數(shù)據(jù)連接信息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 節(jié)點(diǎn),便于后續(xù)使用:
root 節(jié)點(diǎn)的初始化如下:
/*** 獲取根節(jié)點(diǎn)* @param path 配置路徑* @return 元素* @since 0.0.1*/ public static Element getRoot(final String path) {try {// 初始化數(shù)據(jù)庫(kù)連接信息InputStream inputStream = StreamUtil.getInputStream(path);SAXReader reader = new SAXReader();Document document = reader.read(inputStream);return document.getRootElement();} catch (DocumentException e) {throw new MybatisException(e);} }初始化數(shù)據(jù)連接信息
這就是解析 xml 中對(duì)于 dataSource 的配置信息:
/*** 初始化數(shù)據(jù)源** @since 0.0.1*/ private void initDataSource() {// 根據(jù)配置初始化連接信息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 就是對(duì)方法的元數(shù)據(jù)進(jìn)行一些構(gòu)建,比如出參,入?yún)⒌念愋?#xff0c;等等,便于后期使用。
比如我們的 UserMapper.xml 方法內(nèi)容如下:
<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 = ?
方法標(biāo)識(shí):selectById
入?yún)?#xff1a;Long
出參:User
創(chuàng)建 session
如何創(chuàng)建
SqlSession sqlSession = new DefaultSessionFactory(config).openSession();這句話實(shí)際執(zhí)行的是:
@Override public SqlSession openSession() {return new DefaultSqlSession(config, new SimpleExecutor()); }獲取 mapper 實(shí)現(xiàn)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class)這里獲取 mapper,實(shí)際獲取的是什么呢?
實(shí)際上獲取到的是一個(gè)代理。
mybatis 將我們的接口,和實(shí)際 xml 中的 sql 二者通過(guò)動(dòng)態(tài)代理結(jié)合,讓我們調(diào)用 xml 中的 sql 和使用接口方法一樣自然。
獲取代理
getMapper 實(shí)際上是一個(gè)動(dòng)態(tài)代理。
@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); }動(dòng)態(tài)代理的實(shí)現(xiàn)
MapperProxy 的實(shí)現(xiàn)如下:
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);}}代理了什么?
當(dāng)我們執(zhí)行 userMapper.selectById(1L) 時(shí),實(shí)際執(zhí)行的是什么?
實(shí)際執(zhí)行的是 sqlSession.selectOne(mapperMethod, args)
代理實(shí)現(xiàn)
selectOne 是比較核心的內(nèi)容了。
整體實(shí)現(xiàn)
整體如下
public <T> T query(final Config config,MapperMethod method, Object[] args) {try(Connection connection = config.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(method.getSql());) {// 2. 處理參數(shù)parameterHandle(preparedStatement, args);// 3. 執(zhí)行方法preparedStatement.execute();// 4. 處理結(jié)果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,然后構(gòu)建 jdbc 中大家比較熟悉的 PreparedStatement。
然后對(duì)出參和入?yún)⑦M(jìn)行處理,最后返回結(jié)果。
入?yún)⒃O(shè)置
public void setParams(final Object[] objects) {try {for(int i = 0; i < objects.length; i++) {Object value = objects[i];// 目標(biāo)類型,這個(gè)后期可以根據(jù) jdbcType 獲取// jdbc 下標(biāo)從1開(kāi)始statement.setObject(i+1, value);}} catch (SQLException throwables) {throw new MybatisException(throwables);} }針對(duì)我們非常簡(jiǎn)單的例子:
select * from user where id = ?那就是直接把入?yún)⒅械?1L 設(shè)置到占位符 ? 即可。
出參處理
這里主要用到反射,將查詢結(jié)果和 javaBean 做一一映射。
/*** 構(gòu)建結(jié)果* @param resultSet 結(jié)果集合* @return 結(jié)果* @since 0.0.1*/ public Object buildResult(final ResultSet resultSet) {try {// 基本類型,非 java 對(duì)象,直接返回即可。// 可以進(jìn)行抽象Object instance = resultType.newInstance();// 結(jié)果大小的判斷// 為空直接返回,大于1則報(bào)錯(cuò)if(resultSet.next()) {List<Field> fieldList = ClassUtil.getAllFieldList(resultType);for(Field field : fieldList) {Object value = getResult(field, resultSet);ReflectFieldUtil.setValue(field, instance, value);}// 返回設(shè)置值后的結(jié)果return instance;}return null;} catch (InstantiationException | IllegalAccessException | SQLException e) {throw new MybatisException(e);} }到這里,一個(gè)簡(jiǎn)易版的 myabtis 就可以跑起來(lái)了。
當(dāng)然這里還有很多的不足之處,我們后續(xù)都會(huì)一一優(yōu)化。
完整代碼地址
為了便于學(xué)習(xí),完整版本代碼以開(kāi)源:
https://github.com/houbb/mybatishttp://weixin.qq.com/r/GSnk-PfEiar2rbOf93wL<br> (二維碼自動(dòng)識(shí)別)
總結(jié)
以上是生活随笔為你收集整理的mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android tcp socket框架
- 下一篇: 待办事项桌面插件_求一款安卓手机上可添加