長(zhǎng)文干貨 | 手寫(xiě)自定義持久層框架!
Baoxing
讀完需要
速讀僅需 38 分鐘
為何要手寫(xiě)自定義持久層框架?
JDBC 編碼的弊端
會(huì)造成硬編碼問(wèn)題(無(wú)法靈活切換數(shù)據(jù)庫(kù)驅(qū)動(dòng)) 頻繁創(chuàng)建和釋放數(shù)據(jù)庫(kù)連接造成系統(tǒng)資源浪費(fèi) 影響系統(tǒng)性能 sql 語(yǔ)句存在硬編碼,造成代碼不易維護(hù),實(shí)際應(yīng)用中 sql 變化可能較大,變動(dòng) sql 需要改 Java 代碼 使用 preparedStatement 向占有位符號(hào)傳參數(shù)存在硬編碼, 因 sql 語(yǔ)句的 where 條件不確定甚至沒(méi)有where條件,修改 sql 還要修改代碼 系統(tǒng)不易維護(hù) 對(duì)結(jié)果集解析也存在硬編碼, sql變化導(dǎo)致解析代碼變化
更有助于讀 mybatis 持久層框架源碼
JDBC代碼
public?class?jdbcConnection?{
????private?static?Connection?connection?=?null;
????private?static?PreparedStatement?preparedStatement?=?null;
????private?static?ResultSet?resultSet?=?null;
????public?static?void?main(String[]?args)?{
????????try?{
????????????//?加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
????????????Class.forName("com.mysql.jdbc.Driver");
????????????//?通過(guò)驅(qū)動(dòng)管理類(lèi)獲取數(shù)據(jù)庫(kù)連接
????????????connection?=?DriverManager.getConnection("jdbc:mysql://localhost:3306/huodd",?"root",?"1234");
????????????//?定義sql語(yǔ)句???表示占位符
????????????String?sql?=?"select?id,username?from?user?where?id?=??";
????????????//?獲取預(yù)處理對(duì)象?statement
????????????PreparedStatement?preparedStatement?=?(PreparedStatement)?connection.prepareStatement(sql);
????????????//?設(shè)置參數(shù)?第一個(gè)參數(shù)為?sql?語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始)?第二個(gè)參數(shù)為?設(shè)置的參數(shù)值
????????????preparedStatement.setInt(1,?1);
????????????//?向數(shù)據(jù)庫(kù)發(fā)出sql執(zhí)行查詢?查詢出結(jié)果集
????????????resultSet?=?preparedStatement.executeQuery();
????????????//?遍歷查詢結(jié)果集
????????????while?(resultSet.next())?{
????????????????int?id?=?resultSet.getInt("id");
????????????????String?username?=?resultSet.getString("username");
????????????????//?封裝對(duì)象
????????????????User?user?=?new?User();
????????????????user.setId(id);
????????????????user.setUsername(username);
????????????????System.out.println(user);
????????????}
????????}?catch?(Exception?ex)?{
????????????ex.printStackTrace();
????????}?finally?{
????????????try?{
????????????????//?釋放資源
????????????????if?(resultSet?!=?null)?{
????????????????????resultSet.close();
????????????????}
????????????????if?(preparedStatement?!=?null)?{
????????????????????preparedStatement.close();
????????????????}
????????????????if?(connection?!=?null)?{
????????????????????connection.close();
????????????????}
????????????}?catch?(Exception?ex)?{
????????????????ex.printStackTrace();
????????????}
????????}
????}
}
解決問(wèn)題的思路
數(shù)據(jù)庫(kù)頻繁創(chuàng)建連接、釋放資源 -> 連接池 sql語(yǔ)句及參數(shù)硬編碼 -> 配置文件 手動(dòng)解析封裝結(jié)果集 -> 反射、內(nèi)省
編碼前思路整理
創(chuàng)建、讀取配置文件
sqlMapConfig.xml 存放數(shù)據(jù)庫(kù)配置信息 userMapper.xml :存放sql配置信息 根據(jù)配置文件的路徑,加載配置文件成字節(jié)輸入流,存儲(chǔ)在內(nèi)存中Resources#getResourceAsStream(String path) 創(chuàng)建兩個(gè)JavaBean存儲(chǔ)配置文件解析出來(lái)的內(nèi)容 Configuration :核心配置類(lèi) ,存放 sqlMapConfig.xml解析出來(lái)的內(nèi)容 MappedStatement:映射配置類(lèi):存放mapper.xml解析出來(lái)的內(nèi)容
解析配置文件(使用dom4j)
創(chuàng)建類(lèi):SqlSessionFactoryBuilder#build(InputStream in) ? -> 設(shè)計(jì)模式之構(gòu)建者模式 使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到容器對(duì)象(JavaBean)中
創(chuàng)建 SqlSessionFactory 接口及實(shí)現(xiàn)類(lèi)DefaultSqlSessionFactory
SqlSessionFactory對(duì)象,生產(chǎn)sqlSession會(huì)話對(duì)象 ?-> 設(shè)計(jì)模式之工廠模式
創(chuàng)建 SqlSession接口及實(shí)現(xiàn)類(lèi)DefaultSqlSession
定義對(duì)數(shù)據(jù)庫(kù)的CRUD操作 selectList() selectOne() update() delete()
創(chuàng)建Executor接口及實(shí)現(xiàn)類(lèi)SimpleExecutor實(shí)現(xiàn)類(lèi)
query(Configuration configuration, MappedStatement mapStatement, Object... orgs) ?執(zhí)行的就是JDBC代碼
測(cè)試代碼
用到的設(shè)計(jì)模式
構(gòu)建者模式 工廠模式 代理模式
進(jìn)入編碼
1.創(chuàng)建、讀取配置文件
sqlMapConfig.xml 存放數(shù)據(jù)庫(kù)配置信息
<configuration>
????<dataSource>
????????
????????<property?name="driverClass"?value="com.mysql.jdbc.Driver">property>
????????<property?name="jdbcUrl"?value="jdbc:mysql:///huodd">property>
????????<property?name="user"?value="root">property>
????????<property?name="password"?value="1234">property>
????dataSource>
????
????<mapper?resource="userMapper.xml">mapper>
configuration>
userMapper.xml 存放sql配置信息
<mapper?namespace="user">
????
????<select?id="selectList"?resultType="com.huodd.pojo.User"?paramterType="com.huodd.pojo.User">
????????select?*?from?user
????select>
????<select?id="selectOne"?paramterType="com.huodd.pojo.User"?resultType="com.huodd.pojo.User">
????????select?*?from?user?where?id?=?#{id}?and?username?=#{username}
????select>
mapper>
User.java
public?class?User?{
????private?Integer?id;
????private?String?username;
????...?省略getter?setter?方法
????...?省略?toString?方法
}
pom.xml 中引入依賴(lài)
<dependency>
????<groupId>mysqlgroupId>
????<artifactId>mysql-connector-javaartifactId>
????<version>5.1.17version>
dependency>
<dependency>
????<groupId>c3p0groupId>
????<artifactId>c3p0artifactId>
????<version>0.9.1.2version>
dependency>
<dependency>
????<groupId>log4jgroupId>
????<artifactId>log4jartifactId>
????<version>1.2.12version>
dependency>
<dependency>
????<groupId>junitgroupId>
????<artifactId>junitartifactId>
????<version>4.10version>
dependency>
<dependency>
????<groupId>dom4jgroupId>
????<artifactId>dom4jartifactId>
????<version>1.6.1version>
dependency>
<dependency>
????<groupId>jaxengroupId>
????<artifactId>jaxenartifactId>
????<version>1.1.6version>
dependency>
創(chuàng)建兩個(gè)JavaBean對(duì)象 用于存儲(chǔ)解析的配置文件的內(nèi)容(Configuration.java、MappedStatement.java)
public?class?Configuration?{
????//?數(shù)據(jù)源
????private?DataSource?dataSource;
????//map集合?key:statementId?value:MappedStatement
????private?Map?mappedStatementMap?=?new?HashMap<>();
????...?省略getter?setter?方法
}
public?class?MappedStatement?{
????//?id
????private?String?id;
????//?sql?語(yǔ)句
????private?String?sql;
????//?參數(shù)值類(lèi)型
????private?Class>?paramterType;
????//?返回值類(lèi)型
????private?Class>?resultType;
???...?省略getter?setter?方法
}
創(chuàng)建Resources工具類(lèi) 并編寫(xiě)靜態(tài)方法getResourceAsSteam(String path)
public?class?Resources?{
????/**
?????*?根據(jù)配置文件的路徑?將配置文件加載成字節(jié)輸入流?存儲(chǔ)在內(nèi)存中
?????*?@param?path
?????*?@return?InputStream
?????*/
????public?static?InputStream?getResourceAsStream(String?path)?{
????????InputStream?resourceAsStream?=?Resources.class.getClassLoader().getResourceAsStream(path);
????????return?resourceAsStream;
????}
}
2.解析配置文件(使用dom4j)
創(chuàng)建 SqlSessionFactoryBuilder類(lèi) 并添加 build 方法
public?class?SqlSessionFactoryBuilder?{
????public?SqlSessionFactory?build?(InputStream?in)?throws?DocumentException,?PropertyVetoException,?ClassNotFoundException?{
????????//?1.?使用?dom4j?解析配置文件?將解析出來(lái)的內(nèi)容封裝到Configuration中
????????XMLConfigerBuilder?xmlConfigerBuilder?=?new?XMLConfigerBuilder();
????????//?configuration?是已經(jīng)封裝好了sql信息和數(shù)據(jù)庫(kù)信息的對(duì)象
????????Configuration?configuration?=?xmlConfigerBuilder.parseConfig(in);
????????//?2.?創(chuàng)建?SqlSessionFactory?對(duì)象??工廠類(lèi)?主要是生產(chǎn)sqlSession會(huì)話對(duì)象
????????DefaultSqlSessionFactory?defaultSqlSessionFactory?=?new?DefaultSqlSessionFactory(configuration);
????????return?defaultSqlSessionFactory;
????}
}
public?class?XMLConfigerBuilder?{
????private?Configuration?configuration;
????public?XMLConfigerBuilder()?{
????????this.configuration?=?new?Configuration();
????}
????/**
?????*?該方法?使用dom4j對(duì)配置文件進(jìn)行解析?封裝Configuration
?????*?@param?in
?????*?@return
?????*/
?????public?Configuration?parseConfig?(InputStream?in)?throws?DocumentException,?PropertyVetoException,?ClassNotFoundException?{
?????????Document?document?=?new?SAXReader().read(in);
?????????//?
?????????Element?rootElement?=?document.getRootElement();
?????????List?propertyElements?=?rootElement.selectNodes("http://property");
?????????Properties?properties?=?new?Properties();
?????????for?(Element?propertyElement?:?propertyElements)?{
?????????????properties.setProperty(propertyElement.attributeValue("name"),?propertyElement.attributeValue("value"));
?????????}
?????????//?連接池
?????????ComboPooledDataSource?comboPooledDataSource?=?new?ComboPooledDataSource();
?????????comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
?????????comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
?????????comboPooledDataSource.setUser(properties.getProperty("user"));
?????????comboPooledDataSource.setPassword(properties.getProperty("password"));
?????????//?填充?configuration
?????????configuration.setDataSource(comboPooledDataSource);
?????????//?mapper?部分??拿到路徑?->?字節(jié)輸入流?->?dom4j進(jìn)行解析
?????????List?mapperElements?=?rootElement.selectNodes("http://mapper");
?????????XMLMapperBuilder?xmlMapperBuilder?=?new?XMLMapperBuilder(configuration);
?????????for?(Element?mapperElement?:?mapperElements)?{
?????????????String?mapperPath?=?mapperElement.attributeValue("resource");
?????????????InputStream?resourceAsStream?=?Resources.getResourceAsStream(mapperPath);
?????????????xmlMapperBuilder.parse(resourceAsStream);
?????????}
?????????return?configuration;
?????}
}
public?class?XMLMapperBuilder?{
????private?Configuration?configuration;
????public?XMLMapperBuilder(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????public?void?parse(InputStream?inputStream)?throws?DocumentException,?ClassNotFoundException?{
????????Document?document?=?new?SAXReader().read(inputStream);
????????//?
????????Element?rootElement?=?document.getRootElement();
????????String?namespace?=?rootElement.attributeValue("namespace");
????????List?select?=?rootElement.selectNodes("http://select");
????????for?(Element?element?:?select)?{
????????????//?獲取?id?的值
????????????String?id?=?element.attributeValue("id");
????????????String?paramterType?=?element.attributeValue("paramterType");
????????????String?resultType?=?element.attributeValue("resultType");
????????????//?輸入?yún)?shù)?class
????????????Class>?paramterTypeClass?=?getClassType(paramterType);
????????????//?返回結(jié)果?class
????????????Class>?resultTypeClass?=?getClassType(resultType);
????????????//?sql?語(yǔ)句
????????????String?sqlStr?=?element.getTextTrim();
????????????//?封裝?mappedStatement
????????????MappedStatement?mappedStatement?=?new?MappedStatement();
????????????mappedStatement.setId(id);
????????????mappedStatement.setParamterType(paramterTypeClass);
????????????mappedStatement.setResultType(resultTypeClass);
????????????mappedStatement.setSql(sqlStr);
????????????//?statementId
????????????String?key?=?namespace?+?"."?+?id;
????????????//?填充?configuration
????????????configuration.getMappedStatementMap().put(key,?mappedStatement);
????????}
????}
????private?Class>?getClassType(String?paramterType)?throws?ClassNotFoundException?{
????????Class>?aClass?=?Class.forName(paramterType);
????????return?aClass;
????}
}
3.創(chuàng)建 SqlSessionFactory 接口及實(shí)現(xiàn)類(lèi)DefaultSqlSessionFactory
public?interface?SqlSessionFactory?{
????SqlSession?openSession();
}
public?class?DefaultSqlSessionFactory?implements?SqlSessionFactory?{
????private?Configuration?configuration;
????public?DefaultSqlSessionFactory(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????@Override
????public?SqlSession?openSession()?{
????????return?new?DefaultSqlSession(configuration);
????}
}
4. 創(chuàng)建 SqlSession接口及實(shí)現(xiàn)類(lèi)DefaultSqlSession
public?interface?SqlSession?{
?????List?selectList(String?statementId,?Object...?param)?throws?Exception ;
?????T?selectOne(String?statementId,?Object...?params)?throws?Exception;
????void?close()?throws?SQLException;
???
}
public?class?DefaultSqlSession?implements?SqlSession?{
????private?Configuration?configuration;
????//?處理器對(duì)象
????private?Executor?simpleExcutor?=?new?SimpleExecutor();
????public?DefaultSqlSession(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????@Override
????public??List?selectList(String?statementId,?Object...?param)?throws?Exception? {
????????//?完成對(duì)?simpleExcutor里的query方法的調(diào)用
????????MappedStatement?mappedStatement?=?configuration.getMappedStatementMap().get(statementId);
????????List?list?=?simpleExcutor.query(configuration,?mappedStatement,?param);
????????return?list;
????}
????@Override
????public??T?selectOne(String?statementId,?Object...?params)?throws?Exception?{
????????List 5.創(chuàng)建Executor接口及實(shí)現(xiàn)類(lèi)SimpleExecutor實(shí)現(xiàn)類(lèi)
public?interface?Executor?{
?????List?query(Configuration?configuration,?MappedStatement?mappedStatement,?Object...?param)?throws?Exception ;
????void?close()?throws?SQLException;
}
public?class?SimpleExecutor?implements?Executor?{
????private?Connection?connection?=?null;
????@Override
????public??List?query(Configuration?configuration,?MappedStatement?mappedStatement,?Object...?param)?throws?Exception? {
????????//?注冊(cè)驅(qū)動(dòng)?獲取連接
????????connection?=?configuration.getDataSource().getConnection();
????????//?select?*?from?user?where?id?=?#{id}?and?username?=?#{username}
????????String?sql?=?mappedStatement.getSql();
????????//?對(duì)?sql?進(jìn)行處理
????????BoundSql?boundSql?=?getBoundSql(sql);
????????//?select?*?from?where?id?=???and?username?=??
????????String?finalSql?=?boundSql.getSqlText();
????????//?獲取傳入?yún)?shù)類(lèi)對(duì)象
????????Class>?paramterTypeClass?=?mappedStatement.getParamterType();
????????//?獲取預(yù)處理?preparedStatement?對(duì)象
????????PreparedStatement?preparedStatement?=?connection.prepareStatement(finalSql);
????????//?設(shè)置參數(shù)
????????List?parameterMappingList?=?boundSql.getParameterMappingList();
????????for?(int?i?=?0;?i?????????????ParameterMapping?parameterMapping?=?parameterMappingList.get(i);
????????????String?name?=?parameterMapping.getContent();
????????????//?反射??獲取某一個(gè)屬性對(duì)象
????????????Field?declaredField?=?paramterTypeClass.getDeclaredField(name);
????????????//?設(shè)置暴力訪問(wèn)
????????????declaredField.setAccessible(true);
????????????//?參數(shù)傳遞的值
????????????Object?o?=?declaredField.get(param[0]);
????????????//?給占位符賦值
????????????preparedStatement.setObject(i?+?1,?o);
????????}
????????//?執(zhí)行sql
????????ResultSet?resultSet?=?preparedStatement.executeQuery();
????????//?封裝返回結(jié)果集
????????//?獲取返回參數(shù)類(lèi)對(duì)象
????????Class>?resultTypeClass?=?mappedStatement.getResultType();
????????ArrayList?results?=?new?ArrayList<>();
????????while?(resultSet.next())?{
????????????//?取出?resultSet的元數(shù)據(jù)
????????????ResultSetMetaData?metaData?=?resultSet.getMetaData();
????????????E?o?=?(E)?resultTypeClass.newInstance();
????????????int?columnCount?=?metaData.getColumnCount();
????????????for?(int?i?=?1;?i?<=?columnCount;?i++)?{
????????????????//?屬性名/字段名
????????????????String?columnName?=?metaData.getColumnName(i);
????????????????//?屬性值/字段值
????????????????Object?value?=?resultSet.getObject(columnName);
????????????????//?使用反射或者內(nèi)省?根據(jù)數(shù)據(jù)庫(kù)表和實(shí)體的對(duì)應(yīng)關(guān)系?完成封裝
????????????????//?創(chuàng)建屬性描述器?為屬性生成讀寫(xiě)方法
????????????????PropertyDescriptor?propertyDescriptor?=?new?PropertyDescriptor(columnName,?resultTypeClass);
????????????????//?獲取寫(xiě)方法
????????????????Method?writeMethod?=?propertyDescriptor.getWriteMethod();
????????????????//?向類(lèi)中寫(xiě)入值
????????????????writeMethod.invoke(o,?value);
????????????}
????????????results.add(o);
????????}
????????return?results;
????}
????/**
?????*?轉(zhuǎn)換sql語(yǔ)句?完成對(duì)?#{}?的解析工作
?????*?1.?將?#{}?使用?進(jìn)行代替
?????*?2.?解析出?#{}?里面的值進(jìn)行存儲(chǔ)
?????*
?????*?@param?sql?轉(zhuǎn)換前的原sql
?????*?@return
?????*/
????private?BoundSql?getBoundSql(String?sql)?{
????????//?標(biāo)記處理類(lèi):?主要是配合通用解析器?GenericTokenParser?類(lèi)完成對(duì)配置文件等的解析工作?其中TokenHandler?主要完成處理
????????ParameterMappingTokenHandler?parameterMappingTokenHandler?=?new?ParameterMappingTokenHandler();
????????//?GenericTokenParser:?通用的標(biāo)記解析器?完成了代碼片段中的占位符的解析?然后根據(jù)給定的標(biāo)記處理器(?TokenHandler?)?來(lái)進(jìn)行表達(dá)式的處理
????????//?三個(gè)參數(shù):?分別為?openToken?(開(kāi)始標(biāo)記)、?closeToken?(結(jié)束標(biāo)記)、?handler?(標(biāo)記處理器)
????????GenericTokenParser?genericTokenParse?=?new?GenericTokenParser("#{",?"}",?parameterMappingTokenHandler);
????????//?解析出來(lái)的sql
????????String?parseSql?=?genericTokenParse.parse(sql);
????????//?#{}?里面解析出來(lái)的參數(shù)名稱(chēng)
????????List?parameterMappings?=?parameterMappingTokenHandler.getParameterMappings();
????????BoundSql?boundSql?=?new?BoundSql(parseSql,?parameterMappings);
????????return?boundSql;
????}
????@Override
????public?void?close()?throws?SQLException?{
????????connection.close();
????}
}
public?class?BoundSql?{
????//?解析過(guò)后的?sql?語(yǔ)句
????private?String?sqlText;
????//?解析出來(lái)的參數(shù)
????private?List?parameterMappingList?=?new?ArrayList<>();
????
????//?有參構(gòu)造方便創(chuàng)建時(shí)賦值
????public?BoundSql(String?sqlText,?List?parameterMappingList) ?{
????????this.sqlText?=?sqlText;
????????this.parameterMappingList?=?parameterMappingList;
????}
???...?省略getter?setter?方法
}
6.測(cè)試代碼
public?class?IPersistenceTest?{
????@Test
????public?void?test?()?throws?Exception?{
????????InputStream?resourceAsStream?=?Resources.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFactory?sessionFactory?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
????????SqlSession?sqlSession?=?sessionFactory.openSession();
????????User?user?=?new?User();
????????user.setId(1);
????????user.setUsername("bd2star");
????????User?res?=?sqlSession.selectOne("user.selectOne",?user);
????????System.out.println(res);
????????
????????//?關(guān)閉資源
???????sqlSession.close()
????}
}
運(yùn)行結(jié)果如下
User{id=1,?username='bd2star'}
測(cè)試通過(guò) 調(diào)整代碼
創(chuàng)建 接口 Dao及實(shí)現(xiàn)類(lèi)
public?interface?IUserDao?{
????//?查詢所有用戶
????public?List?selectList()?throws?Exception ;
????//?根據(jù)條件進(jìn)行用戶查詢
????public?User?selectOne(User?user)?throws?Exception;
}
public?class?UserDaoImpl?implements?IUserDao?{
????@Override
????public?List?findAll()?throws?Exception? {
????????InputStream?resourceAsStream?=?Resources.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFactory?sessionFactory?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
????????SqlSession?sqlSession?=?sessionFactory.openSession();
????????List?res?=?sqlSession.selectList("user.selectList");
????????sqlSession.close();
????????return?res;
????}
????@Override
????public?User?findByCondition(User?user)?throws?Exception?{
????????InputStream?resourceAsStream?=?Resources.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFactory?sessionFactory?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
????????SqlSession?sqlSession?=?sessionFactory.openSession();
????????User?res?=?sqlSession.selectOne("user.selectOne",?user);
????????sqlSession.close();
????????return?res;
????}
}
調(diào)整測(cè)試方法
public?class?IPersistenceTest?{
????@Test
????public?void?test?()?throws?Exception?{
????????User?user?=?new?User();
????????user.setId(1);
????????user.setUsername("bd2star");
????????IUserDao?userDao?=?new?UserDaoImpl();
????????User?res?=?userDao.findByCondition(user);
????????System.out.println(res);
????}
}
運(yùn)行結(jié)果如下
User{id=1,?username='bd2star'}
測(cè)試通過(guò)
7.補(bǔ)充
huodd.sql
--新建數(shù)據(jù)庫(kù)
CREATE?DATABASE?huodd;
--使用數(shù)據(jù)庫(kù)
use?huodd;
--創(chuàng)建表
CREATE?TABLE?`user`??(
??`id`?int(11)?NOT?NULL,
??`username`?varchar(255)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NULL?DEFAULT?NULL,
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Compact;
--?插入測(cè)試數(shù)據(jù)
INSERT?INTO?`user`?VALUES?(1,?'bd2star');
INSERT?INTO?`user`?VALUES?(2,?'bd3star');
用到的工具類(lèi)
GenericTokenParser.java
public?class?GenericTokenParser?{
??private?final?String?openToken;?//開(kāi)始標(biāo)記
??private?final?String?closeToken;?//結(jié)束標(biāo)記
??private?final?TokenHandler?handler;?//標(biāo)記處理器
??public?GenericTokenParser(String?openToken,?String?closeToken,?TokenHandler?handler)?{
????this.openToken?=?openToken;
????this.closeToken?=?closeToken;
????this.handler?=?handler;
??}
??/**
???*?解析${}和#{}
???*?@param?text
???*?@return
???*?該方法主要實(shí)現(xiàn)了配置文件、腳本等片段中占位符的解析、處理工作,并返回最終需要的數(shù)據(jù)。
???*?其中,解析工作由該方法完成,處理工作是由處理器handler的handleToken()方法來(lái)實(shí)現(xiàn)
???*/
??public?String?parse(String?text)?{
????//?驗(yàn)證參數(shù)問(wèn)題,如果是null,就返回空字符串。
????if?(text?==?null?||?text.isEmpty())?{
??????return?"";
????}
????//?下面繼續(xù)驗(yàn)證是否包含開(kāi)始標(biāo)簽,如果不包含,默認(rèn)不是占位符,直接原樣返回即可,否則繼續(xù)執(zhí)行。
????int?start?=?text.indexOf(openToken,?0);
????if?(start?==?-1)?{
??????return?text;
????}
???//?把text轉(zhuǎn)成字符數(shù)組src,并且定義默認(rèn)偏移量offset=0、存儲(chǔ)最終需要返回字符串的變量builder,
????// text變量中占位符對(duì)應(yīng)的變量名expression。判斷start是否大于-1(即text中是否存在openToken),如果存在就執(zhí)行下面代碼
????char[]?src?=?text.toCharArray();
????int?offset?=?0;
????final?StringBuilder?builder?=?new?StringBuilder();
????StringBuilder?expression?=?null;
????while?(start?>?-1)?{
?????//?判斷如果開(kāi)始標(biāo)記前如果有轉(zhuǎn)義字符,就不作為openToken進(jìn)行處理,否則繼續(xù)處理
??????if?(start?>?0?&&?src[start?-?1]?==?'\\')?{
????????builder.append(src,?offset,?start?-?offset?-?1).append(openToken);
????????offset?=?start?+?openToken.length();
??????}?else?{
????????//重置expression變量,避免空指針或者老數(shù)據(jù)干擾。
????????if?(expression?==?null)?{
??????????expression?=?new?StringBuilder();
????????}?else?{
??????????expression.setLength(0);
????????}
????????builder.append(src,?offset,?start?-?offset);
????????offset?=?start?+?openToken.length();
????????int?end?=?text.indexOf(closeToken,?offset);
????????while?(end?>?-1)?{////存在結(jié)束標(biāo)記時(shí)
??????????if?(end?>?offset?&&?src[end?-?1]?==?'\\')?{//如果結(jié)束標(biāo)記前面有轉(zhuǎn)義字符時(shí)
????????????//?this?close?token?is?escaped.?remove?the?backslash?and?continue.
????????????expression.append(src,?offset,?end?-?offset?-?1).append(closeToken);
????????????offset?=?end?+?closeToken.length();
????????????end?=?text.indexOf(closeToken,?offset);
??????????}?else?{//不存在轉(zhuǎn)義字符,即需要作為參數(shù)進(jìn)行處理
????????????expression.append(src,?offset,?end?-?offset);
????????????offset?=?end?+?closeToken.length();
????????????break;
??????????}
????????}
????????if?(end?==?-1)?{
??????????//?close?token?was?not?found.
??????????builder.append(src,?start,?src.length?-?start);
??????????offset?=?src.length;
????????}?else?{
??????????//首先根據(jù)參數(shù)的key(即expression)進(jìn)行參數(shù)處理,返回?作為占位符
??????????builder.append(handler.handleToken(expression.toString()));
??????????offset?=?end?+?closeToken.length();
????????}
??????}
??????start?=?text.indexOf(openToken,?offset);
????}
????if?(offset???????builder.append(src,?offset,?src.length?-?offset);
????}
????return?builder.toString();
??}
}
ParameterMapping.java
public?class?ParameterMapping?{
????private?String?content;
????public?ParameterMapping(String?content)?{
????????this.content?=?content;
????}
????...?省略getter?setter?方法
}
ParameterMappingTokenHandler.java
public?class?ParameterMappingTokenHandler?implements?TokenHandler?{
???private?List?parameterMappings?=?new?ArrayList();
???//?context是參數(shù)名稱(chēng)?#{id}?#{username}
???public?String?handleToken(String?content)?{
??????parameterMappings.add(buildParameterMapping(content));
??????return?"?";
???}
???private?ParameterMapping?buildParameterMapping(String?content)?{
??????ParameterMapping?parameterMapping?=?new?ParameterMapping(content);
??????return?parameterMapping;
???}
???public?List?getParameterMappings()? {
??????return?parameterMappings;
???}
???public?void?setParameterMappings(List?parameterMappings) ?{
??????this.parameterMappings?=?parameterMappings;
???}
}
TokenHandler.java
public?interface?TokenHandler?{
??String?handleToken(String?content);
}
繼續(xù)優(yōu)化自定義框架
通過(guò)上述自定義框架,我們解決了JDBC操作數(shù)據(jù)庫(kù)帶來(lái)的一些問(wèn)題,例如頻繁創(chuàng)建釋放數(shù)據(jù)庫(kù)連接,硬編碼,手動(dòng)封裝返回結(jié)果等問(wèn)題
但從測(cè)試類(lèi)可以發(fā)現(xiàn)新的問(wèn)題
dao 的實(shí)現(xiàn)類(lèi)存在重復(fù)代碼 整個(gè)操作的過(guò)程模板重復(fù) (如創(chuàng)建 SqlSession 調(diào)用 SqlSession方法 關(guān)閉 SqlSession) dao 的實(shí)現(xiàn)類(lèi)中存在硬編碼,如調(diào)用 sqlSession 方法時(shí) 參數(shù) statementId 的硬編碼
解決方案
通過(guò)代碼模式來(lái)創(chuàng)建接口的代理對(duì)象
1.添加getMapper方法
刪除dao的實(shí)現(xiàn)類(lèi) UserDaoImpl.java 我們通過(guò)代碼來(lái)實(shí)現(xiàn)原來(lái)由實(shí)現(xiàn)類(lèi)執(zhí)行的邏輯
在 SqlSession 中添加 getMapper 方法
public?interface?SqlSession?{
????T?getMapper(Class>?mapperClass);
}
2. 實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)方法
DefaultSqlSession 類(lèi)中實(shí)現(xiàn) getMapper 方法
@Override
public??T?getMapper(Class>?mapperClass)?{
????//?使用?JDK?動(dòng)態(tài)代理?來(lái)為?Dao?接口生成代理對(duì)象?并返回
????Object?proxyInstance?=?Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),?new?Class[]{mapperClass},?new?InvocationHandler()?{
????????/**
?????????*
?????????*?@param?proxy?當(dāng)前代理對(duì)象的引用
?????????*?@param?method?當(dāng)前被調(diào)用方法的引用
?????????*?@param?args?傳遞的參數(shù)
?????????*?@return
?????????*?@throws?Throwable
?????????*/
????????@Override
????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????????//?底層都還是去執(zhí)行?JDBC?代碼??->?根據(jù)不同情況?調(diào)用?selectList()?或者?selectOne()
????????????//?準(zhǔn)備參數(shù)??1.?statmentId?sql語(yǔ)句的唯一標(biāo)識(shí)??namespace.id?=?接口全限定名.方法名
????????????//??????????2.?params?->?args
????????????
????????????//?拿到的是方法名?findAll
????????????String?methodName?=?method.getName();
????????????//?拿到該類(lèi)的全限定類(lèi)名?com.huodd.dao.IUserDao
????????????String?className?=?method.getDeclaringClass().getName();
????????????String?statmentId?=?className?+?"."?+?methodName;
????????????//?獲取被調(diào)用方法的返回值類(lèi)型
????????????Type?genericReturnType?=?method.getGenericReturnType();
????????????//?判斷是否進(jìn)行了?泛型類(lèi)型參數(shù)化
????????????if?(genericReturnType?instanceof?ParameterizedType)?{
????????????????List 3.調(diào)整mapper.xml配置文件
這里要注意兩點(diǎn)
namespace 與 dao 接口的全限定類(lèi)名保持一致 id 與 dao 接口中定義的方法名保持一致
"com.huodd.dao.IUserDao">
????
????
????
4. 進(jìn)入測(cè)試
public?class?IPersistenceTest?{
????@Test
????public?void?test?()?throws?Exception?{
????????InputStream?resourceAsStream?=?Resources.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFactory?sessionFactory?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
????????SqlSession?sqlSession?=?sessionFactory.openSession();
????????User?user?=?new?User();
????????user.setId(1);
????????user.setUsername("bd2star");
??//?此時(shí)返回的?userDao?就是代理對(duì)象?所以它的類(lèi)型就是?Proxy
????????IUserDao?userDao?=?sqlSession.getMapper(IUserDao.class);
????????//?userDao?是代理對(duì)象??調(diào)用了接口中的?findAll()??代理對(duì)象調(diào)用接口中任意方法?都會(huì)執(zhí)行?invoke()
????????List?users?=?userDao.findAll();
????????System.out.println(users);
????????User?res?=?userDao.findByCondition(user);
????????System.out.println(res);
????}
}
運(yùn)行結(jié)果如下
[User{id=1,?username='bd2star'},?User{id=2,?username='bd3star'}]
User{id=1,?username='bd2star'}
目錄結(jié)構(gòu)調(diào)整
將代碼分為兩個(gè)模塊
提供端(自定義持久層框架-本質(zhì)就是對(duì)JDBC代碼的封裝) 使用端 (引用持久層框架的jar ) 包含數(shù)據(jù)庫(kù)配置信息 包含sql配置信息 包含sql語(yǔ)句 參數(shù)類(lèi)型 返回值類(lèi)型
項(xiàng)目目錄結(jié)構(gòu)最終為
提供端

使用端

源碼地址
https://gitee.com/bx2star/mybatis-learning.git
—————END—————
推薦閱讀:
最近面試BAT,整理一份面試資料《Java面試BAT通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
明天見(jiàn)(??ω??)??
