自己動(dòng)手寫(xiě)一個(gè)持久層框架
1. JDBC問(wèn)題分析
我們來(lái)看一段JDBC的代碼:
public?static?void?main(String[]?args)?{
????????Connection?connection?=?null;
????????PreparedStatement?preparedStatement?=?null;
????????ResultSet?resultSet?=?null;
????????try?{
????????????//1.?加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
????????????Class.forName("com.mysql.jdbc.Drive");
????????????//2.?通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
????????????connection?=?DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8",
????????????????????"root","root");
????????????//3. 定義SQL語(yǔ)句??表示占位符
????????????String?sql?=?"SELECT?*?FROM?user?WHERE?username?=??";
????????????//4.?獲取預(yù)處理對(duì)象Statement
????????????preparedStatement?=?connection.prepareStatement(sql);
????????????//5.?設(shè)置參數(shù),第一個(gè)參數(shù)為SQL語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始),第二個(gè)參數(shù)為設(shè)置的參數(shù)值
????????????preparedStatement.setString(1,"tom");
????????????//6.?向數(shù)據(jù)庫(kù)發(fā)出SQL執(zhí)行查詢,查詢出結(jié)果集
????????????resultSet?=?preparedStatement.executeQuery();
????????????//7.?遍歷查詢結(jié)果集
????????????while?(resultSet.next()){
????????????????int?id?=?resultSet.getInt("id");
????????????????String?userName?=?resultSet.getString("username");
????????????????//封裝User
????????????????user.setId(id);
????????????????user.setUserName(userName);
????????????}
????????????System.out.println(user);
????????}?catch?(ClassNotFoundException?e)?{
????????????e.printStackTrace();
????????}?catch?(SQLException?throwables)?{
????????????throwables.printStackTrace();
????????}
????}
可以看到,直接使用JDBC開(kāi)發(fā)是存在一些問(wèn)題的,我們來(lái)分析下:
問(wèn)題分析:
數(shù)據(jù)庫(kù)配置信息存在硬編碼問(wèn)題 頻繁創(chuàng)建、釋放數(shù)據(jù)庫(kù)鏈接
//1.?加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Drive");
//2.?通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
connection?=?DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8","root","root");
sql語(yǔ)句、設(shè)置參數(shù)、獲取結(jié)果集均存在硬編碼問(wèn)題
//3. 定義SQL語(yǔ)句??表示占位符
String?sql?=?"SELECT?*?FROM?user?WHERE?username?=??";
//4.?獲取預(yù)處理對(duì)象Statement
preparedStatement?=?connection.prepareStatement(sql);
//5.?設(shè)置參數(shù),第一個(gè)參數(shù)為SQL語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始),第二個(gè)參數(shù)為設(shè)置的參數(shù)值
?preparedStatement.setString(1,"tom");
?//6.?向數(shù)據(jù)庫(kù)發(fā)出SQL執(zhí)行查詢,查詢出結(jié)果集
?resultSet?=?preparedStatement.executeQuery();
??????int?id?=?resultSet.getInt("id");
??????String?userName?=?resultSet.getString("username");
手動(dòng)封裝返回結(jié)果集 較為繁瑣
//7.?遍歷查詢結(jié)果集
while?(resultSet.next()){
????int?id?=?resultSet.getInt("id");
????String?userName?=?resultSet.getString("username");
????//封裝User
????user.setId(id);
????user.setUserName(userName);
?}
?System.out.println(user);
解決思路:
寫(xiě)在配置文件中 連接池(c3p0、dbcp、德魯伊...) 配置文件 (和1放一起嗎?No,經(jīng)常變動(dòng)和不經(jīng)常變動(dòng)的不要放在一起) 反射、內(nèi)省
下面根據(jù)這個(gè)解決思路,自己動(dòng)手寫(xiě)一個(gè)持久層框架,寫(xiě)框架之前分析這個(gè)框架需要做什么
2. 自定義框架思路分析
使用端(項(xiàng)目):
引入自定義持久層框架的jar包 提供兩部分配置信息:
數(shù)據(jù)庫(kù)配置信息 SQL配置信息(SQL語(yǔ)句)
使用配置文件來(lái)提供這些信息: sqlMapConfig.xml :存放數(shù)據(jù)庫(kù)的配置信息 mapper.xml :存放SQL配置信息
自定義持久層框架(工程):
持久層框架的本質(zhì)就是對(duì)JDBC代碼進(jìn)行了封裝
加載配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)內(nèi)存中
“
Q:getResourceAsStearm方法需要執(zhí)行兩次分別加載sqlMapConfig額和mapper嗎?
A:可以但沒(méi)必要,我們可以在sqlMapConfig.xml中寫(xiě)入mapper.xml的全路徑即可
”創(chuàng)建Resources類 方法:getResourceAsStream(String path) 創(chuàng)建兩個(gè)javaBean:(容器對(duì)象):存放的就是配置文件解析出來(lái)的內(nèi)容
Configuration:核心配置類:存放sqlMapConfig.xml解析出來(lái)的內(nèi)容 MappedStatement:映射配置類:存放mapper.xml解析出來(lái)的內(nèi)容 解析配置文件:使用dom4j
創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in) 這個(gè)流就是剛才存在內(nèi)存中的 使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到容器對(duì)象中 創(chuàng)建SqlSessionFactory對(duì)象;生產(chǎn)sqlSession:會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象) 創(chuàng)建sqlSessionFactory接口及實(shí)現(xiàn)類DefaultSqlSessionFactory
openSession(); 生產(chǎn)sqlSession 創(chuàng)建SqlSession接口及實(shí)現(xiàn)類DefaultSession
selectList() selectOne() update() delete() ... 定義對(duì)數(shù)據(jù)庫(kù)的CRUD操作,例如: 創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類
query(Configuration con,MappedStatement ms,Object ...param);執(zhí)行JDBC代碼, Object ...param具體的參數(shù)值,可變參;
3. 創(chuàng)建表并編寫(xiě)測(cè)試類
SET?NAMES?utf8mb4;
SET?FOREIGN_KEY_CHECKS?=?0;
--?----------------------------
--?Table?structure?for?user
--?----------------------------
DROP?TABLE?IF?EXISTS?`user`;
CREATE?TABLE?`user`??(
??`id`?int(11)?NOT?NULL?AUTO_INCREMENT,
??`username`?varchar(50)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?AUTO_INCREMENT?=?4?CHARACTER?SET?=?utf8?COLLATE?=?utf8_general_ci?ROW_FORMAT?=?Dynamic;
--?----------------------------
--?Records?of?user
--?----------------------------
INSERT?INTO?`user`?VALUES?(1,?'lucy');
INSERT?INTO?`user`?VALUES?(2,?'tom');
INSERT?INTO?`user`?VALUES?(3,?'jack');
SET?FOREIGN_KEY_CHECKS?=?1;
1. 創(chuàng)建一個(gè)Maven項(xiàng)目—— Ipersistence_test
2. 在resource中創(chuàng)建sqlMapConfig.xml 和 UserMapper.xml
UserMapper.xml
<mapper?namespace="user">
????
????<select?id="selectList"?resultType="com.dxh.pojo.User">
????????select?*?from?user
????select>
????<select?id="selectOne"?resultType="com.dxh.pojo.User"?paramterType="com.dxh.pojo.User">
????????select?*?from?user?where?id?=?#{id}?and?username?=?#{userName}
????select>
mapper>
“Q:為什么要有namespace和id ?A:當(dāng)一個(gè)
”*Mapper.xml中有多條sql時(shí),無(wú)法區(qū)分具體是哪一條所以增加 id 如果有UserMapper.xml和ProductMapper.xml,假設(shè)他們的查詢的id都為”selectList“,那么將無(wú)法區(qū)分具體是查詢user還是查詢product的。所以增加 namespacenamespace.id 組成sql的唯一標(biāo)識(shí),也稱為statementId
sqlMapConfig.xml
<configuration>
????
????<dataSource>
????????<property?name="driverClass"?value="com.mysql.jdbc.Driver">property>
????????<property?name="jdbcUrl"?value="jdbc:mysql:///zdy_mybatis">property>
????????<property?name="username"?value="root">property>
????????<property?name="password"?value="root">property>
????dataSource>
????
????<mapper?resource="UserMapper.xml">mapper>
configuration>
4. 開(kāi)始編寫(xiě)持久層框架
自定義持久層框架(工程):
“本質(zhì)就是對(duì)JDBC代碼進(jìn)行了封裝
”
加載配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)內(nèi)存中
創(chuàng)建Resources類 方法:getResourceAsStream(String path) 創(chuàng)建兩個(gè)javaBean:(容器對(duì)象):存放的就是配置文件解析出來(lái)的內(nèi)容
Configuration:核心配置類:存放sqlMapConfig.xml解析出來(lái)的內(nèi)容 MappedStatement:映射配置類:存放mapper.xml解析出來(lái)的內(nèi)容 解析配置文件:使用dom4j
創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in) 這個(gè)流就是剛才存在內(nèi)存中的 使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到容器對(duì)象中 創(chuàng)建SqlSessionFactory對(duì)象;生產(chǎn)sqlSession:會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象) 創(chuàng)建sqlSessionFactory接口及實(shí)現(xiàn)類DefaultSqlSessionFactory
openSession(); 生產(chǎn)sqlSession 創(chuàng)建SqlSession接口及實(shí)現(xiàn)類DefaultSession
定義對(duì)數(shù)據(jù)庫(kù)的CRUD操作 創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類
query(Configuration con,MappedStatement ms,Object ...param);執(zhí)行JDBC代碼, Object ...param具體的參數(shù)值,可變參;
我們之前已經(jīng)對(duì)持久層框架進(jìn)行了分析,需要做6部分組成,如下:
1. 加載配置文件
我們要把用戶端的配置文件成字節(jié)輸入流并存到內(nèi)存中:
新建Resource類,提供一個(gè)static InputStream getResourceAsStream(String path)方法,并返回inputstream
package?com.dxh.io;
import?java.io.InputStream;
public?class?Resource?{
????//根據(jù)配置文件的路徑,將配置文件加載成字節(jié)輸入流,存儲(chǔ)在內(nèi)存中
????public?static?InputStream?getResourceAsStream(String?path){
????????InputStream?resourceAsStream?=?Resource.class.getClassLoader().getResourceAsStream(path);
????????return?resourceAsStream;
????}
}
2. 創(chuàng)建JavaBean(容器對(duì)象)
之前我們說(shuō)到,要把解析出來(lái)的配置文件封裝成對(duì)象。
MappedStatement (存放SQL信息) Configuration (存放數(shù)據(jù)庫(kù)配置信息)
//?MappedStatement,我們存放SQL的信息?
package?com.dxh.pojo;
public?class?MappedStatement?{
????//?id標(biāo)識(shí)
????private?String?id;
????//返回值類型
????private?String?resultType;
????//參數(shù)值類型
????private?String?paramterType;
????//sql語(yǔ)句
????private?String?sql;
????
??getset省略...
}
這里我們把封裝好的MappedStatement對(duì)象也放在Configuration中,同時(shí)我們不存放數(shù)據(jù)庫(kù)的url、username...了,直接存放DataSource
package?com.dxh.pojo;
import?javax.sql.DataSource;
import?java.util.HashMap;
import?java.util.Map;
public?class?Configuration?{
????private?DataSource?dataSource;
????/**
?????*?key?statementId??(就是namespace.id)
?????*?value:封裝好的MappedStatement對(duì)象
?????*/
????Map?mappedStatementMap?=?new?HashMap<>();
?
????getset省略...
}
3.解析xml文件
這一步我們解析兩個(gè)xml文件sqlMapConfig.xml、mapper.xml
我們首先把解析的過(guò)程封裝起來(lái):新建XMLConfigBuild.java
package?com.dxh.config;
import?com.dxh.io.Resource;
import?com.dxh.pojo.Configuration;
import?com.mchange.v2.c3p0.ComboPooledDataSource;
import?org.dom4j.Document;
import?org.dom4j.DocumentException;
import?org.dom4j.Element;
import?org.dom4j.io.SAXReader;
import?java.beans.PropertyVetoException;
import?java.io.InputStream;
import?java.util.List;
import?java.util.Properties;
public?class?XMLConfigBuild?{
????private?Configuration?configuration;
????public?XMLConfigBuild()?{
????????this.configuration?=?new?Configuration();
????}
????/**
?????*?該方法就是將配置文件進(jìn)行解析(dom4j),封裝Configuration
?????*/
????public?Configuration?parseConfig(InputStream?inputStream)?throws?DocumentException,?PropertyVetoException?{
????????Document?document?=?new?SAXReader().read(inputStream);
????????//
????????Element?rootElement?=?document.getRootElement();
????????List?list?=?rootElement.selectNodes("http://property");
????????Properties?properties?=?new?Properties();
????????for?(Element?element?:?list)?{
????????????String?name?=?element.attributeValue("name");
????????????String?value?=?element.attributeValue("value");
????????????properties.setProperty(name,value);
????????}
??//C3P0連接池
????????ComboPooledDataSource?comboPooledDataSource?=?new?ComboPooledDataSource();
????????comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
????????comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
????????comboPooledDataSource.setUser(properties.getProperty("username"));
????????comboPooledDataSource.setPassword(properties.getProperty("password"));
????????configuration.setDataSource(comboPooledDataSource);
????????//mapper.xml解析?:拿到路徑--字節(jié)輸入流---dom4j解析
????????List?mapperList?=?rootElement.selectNodes("http://mapper");
????????for?(Element?element?:?mapperList)?{
????????????//拿到路徑
????????????String?mapperPath?=?element.attributeValue("resource");
????????????//字節(jié)輸入流
????????????InputStream?resourceAsStream?=?Resource.getResourceAsStream(mapperPath);
????????????//dom4j解析
????????????//??因?yàn)榻馕鐾瓿珊蟮腗appedStatement要放在Configuration里,所以傳入一個(gè)configuration進(jìn)去
????????????XMLMapperBuild?xmlMapperBuild?=?new?XMLMapperBuild(configuration);
????????????xmlMapperBuild.parse(resourceAsStream);
????????}
????????return?configuration;
????}
}
3.1 解析Mapper.xml文件:
package?com.dxh.config;
import?com.dxh.pojo.Configuration;
import?com.dxh.pojo.MappedStatement;
import?org.dom4j.Document;
import?org.dom4j.DocumentException;
import?org.dom4j.Element;
import?org.dom4j.io.SAXReader;
import?java.io.InputStream;
import?java.util.List;
public?class?XMLMapperBuild?{
????private?Configuration?configuration;
????public?XMLMapperBuild(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????public?void?parse(InputStream?inputStream)?throws?DocumentException?{
????????Document?document?=?new?SAXReader().read(inputStream);
????????Element?rootElement?=?document.getRootElement();
????????String?namespace?=?rootElement.attributeValue("namespace");
????????List?list?=?rootElement.selectNodes("http://select");
????????for?(Element?element?:?list)?{
????????????String?id?=?element.attributeValue("id");
????????????String?resultType?=?element.attributeValue("resultType");
????????????String?paramterType?=?element.attributeValue("paramterType");
????????????String?sqlText?=?element.getTextTrim();
????????????MappedStatement?mappedStatement?=?new?MappedStatement();
????????????mappedStatement.setId(id);
????????????mappedStatement.setParamterType(paramterType);
????????????mappedStatement.setResultType(resultType);
????????????mappedStatement.setSql(sqlText);
????????????String?key?=?namespace+"."+id;
????????????configuration.getMappedStatementMap().put(key,mappedStatement);
????????}
????}
}
很容易理解,因?yàn)槲覀兘馕龊笠祷?code style>Configuration對(duì)象,所以我們需要聲明一個(gè)Configuration 并初始化。
我們把加載文件后的流傳入,通過(guò)dom4j解析,并通過(guò)ComboPooledDataSource(C3P0連接池)生成我們需要的DataSource,并存入Configuration對(duì)象中。
Mapper.xml解析方式同理。
3.2 創(chuàng)建SqlSessionFactoryBuilder類:有了上述兩個(gè)解析方法后,我們創(chuàng)建一個(gè)類,用來(lái)調(diào)用這個(gè)方法,同時(shí)這個(gè)類返回SqlSessionFacetory
SqlSessionFacetory:用來(lái)生產(chǎn)sqlSession:sqlSession就是會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象)
package?com.dxh.sqlSession;
import?com.dxh.config.XMLConfigBuild;
import?com.dxh.pojo.Configuration;
import?org.dom4j.DocumentException;
import?java.beans.PropertyVetoException;
import?java.io.InputStream;
public?class?SqlSessionFacetoryBuild?{
????public?SqlSessionFacetory?build(InputStream?in)?throws?DocumentException,?PropertyVetoException?{
????????//1.?使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到configuration中
????????XMLConfigBuild?xmlConfigBuild?=?new?XMLConfigBuild();
????????Configuration?configuration?=?xmlConfigBuild.parseConfig(in);
????????//2. 創(chuàng)建sqlSessionFactory對(duì)象?工廠類:生產(chǎn)sqlSession:會(huì)話對(duì)象,與數(shù)據(jù)庫(kù)交互的增刪改查都封裝在sqlSession中
????????DefaultSqlSessionFactory?sqlSessionFacetory?=?new?DefaultSqlSessionFactory(configuration);
????????return?sqlSessionFacetory;
????}
}
4. 創(chuàng)建SqlSessionFacetory接口和實(shí)現(xiàn)類
基于開(kāi)閉原則我們創(chuàng)建SqlSessionFacetory接口和實(shí)現(xiàn)類DefaultSqlSessionFactory
接口中我們定義openSession()方法,用于生產(chǎn)SqlSession
package?com.dxh.sqlSession;
public?interface?SqlSessionFacetory?{
????public?SqlSession?openSession();
}
package?com.dxh.sqlSession;
import?com.dxh.pojo.Configuration;
public?class?DefaultSqlSessionFactory?implements?SqlSessionFacetory{
????private?Configuration?configuration;
????public?DefaultSqlSessionFactory(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????@Override
????public?SqlSession?openSession()?{
????????return?new?DefaultSqlSession(configuration);
????}
}
同樣我們?cè)?code style>DefaultSqlSessionFactory中傳入Configuration,Configuration需要我們一直往下傳遞
5.創(chuàng)建SqlSession接口以及它的實(shí)現(xiàn)類
在接口中,我定義兩個(gè)方法:
因?yàn)閰?shù)類型和個(gè)數(shù)我們都不知道,所以我們使用泛型,同時(shí),傳入statementId(namespace、. 、id 組成)
package?com.dxh.sqlSession;
import?java.util.List;
public?interface?SqlSession?{
????//查詢多條
????public??List?selectList(String?statementId,Object...?params)?throws?Exception ;
????//根據(jù)條件查詢單個(gè)
????public??T?selectOne(String?statementId,Object...?params)?throws?Exception;
}
package?com.dxh.sqlSession;
import?com.dxh.pojo.Configuration;
import?java.util.List;
public?class?DefaultSqlSession?implements?SqlSession?{
????private?Configuration?configuration;
????public?DefaultSqlSession(Configuration?configuration)?{
????????this.configuration?=?configuration;
????}
????@Override
????public??List?selectList(String?statementId,?Object...?params)?throws?Exception? {
????????//將要完成對(duì)simpleExecutor里的query方法調(diào)用
????????SimpleExecutor?simpleExecutor?=?new?SimpleExecutor();
????????List 這里selectOne方法和selectList方法的參數(shù)結(jié)構(gòu)都是一樣的,所以我們可以通過(guò)selectList.get(0)的方式得到一個(gè)返回結(jié)果。而selectList中則是重點(diǎn),我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象SimpleExecutor并在其中執(zhí)行SQL
6.創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類
package?com.dxh.sqlSession;
import?com.dxh.pojo.Configuration;
import?com.dxh.pojo.MappedStatement;
import?java.sql.SQLException;
import?java.util.List;
public?interface?Executor?{
????/**
?????*
?????*?@param?configuration?數(shù)據(jù)庫(kù)配置信息
?????*?@param?mappedStatement?SQL配置信息
?????*?@param?params?可變參
?????*?@return
?????*/
????public??List?query(Configuration?configuration,?MappedStatement?mappedStatement,Object...?params)?throws?SQLException,?Exception ;
}
package?com.dxh.sqlSession;
import?com.dxh.config.BoundSql;
import?com.dxh.pojo.Configuration;
import?com.dxh.pojo.MappedStatement;
import?com.dxh.utils.GenericTokenParser;
import?com.dxh.utils.ParameterMapping;
import?com.dxh.utils.ParameterMappingTokenHandler;
import?java.beans.PropertyDescriptor;
import?java.lang.reflect.Field;
import?java.lang.reflect.Method;
import?java.sql.*;
import?java.util.ArrayList;
import?java.util.List;
/**
?*?@author?https://github.com/CoderXiaohui
?*?@Description
?*?@Date?2020-11-07?22:27
?*/
public?class?SimpleExecutor?implements?Executor{
????/**
?????*??就是在執(zhí)行JDBC的代碼
?????*/
????@Override
????public??List?query(Configuration?configuration,?MappedStatement?mappedStatement,?Object...?params)?throws?Exception? {
????????//1.?注冊(cè)驅(qū)動(dòng),獲取鏈接
????????Connection?connection?=?configuration.getDataSource().getConnection();
????????//2.?獲取SQL語(yǔ)句
????????//假設(shè)獲取的SQL是?:?select?*?from?user?where?id?=?#{id}?and?username?=?#{userName}?JDBC是無(wú)法識(shí)別的,
????????//?所以要轉(zhuǎn)換sql :select * from user where id =??and username = ? ,轉(zhuǎn)換過(guò)程中還需要對(duì)#{}中的值進(jìn)行解析存儲(chǔ)
????????String?sql?=?mappedStatement.getSql();
????????BoundSql?boundSql?=?getBoundSql(sql);
????????//3. 獲取預(yù)處理對(duì)象:preparedStatement
????????PreparedStatement?preparedStatement?=?connection.prepareStatement(boundSql.getSqlText());
????????//4.?設(shè)置參數(shù)
????????????//獲取到參數(shù)的全路徑
????????String?paramterType?=?mappedStatement.getParamterType();
????????Class>??paramterTypeClass?=?getClassType(paramterType);
????????List?parameterMappingList?=?boundSql.getParameterMappingList();
????????for?(int?i?=?0;?i?????????????ParameterMapping?parameterMapping?=?parameterMappingList.get(i);
????????????String?content?=?parameterMapping.getContent();
????????????//反射
????????????Field?declaredField?=?paramterTypeClass.getDeclaredField(content);
????????????//暴力訪問(wèn),防止它是私有的
????????????declaredField.setAccessible(true);
????????????Object?o?=?declaredField.get(params[0]);
????????????//下標(biāo)從1開(kāi)始
????????????preparedStatement.setObject(i+1,o);
????????}
????????//5.?執(zhí)行sql
????????ResultSet?resultSet?=?preparedStatement.executeQuery();
????????String?resultType?=?mappedStatement.getResultType();
????????Class>?resultTypeClass?=?getClassType(resultType);
????????ArrayList?objects?=?new?ArrayList<>();
????????//6.?封裝返回結(jié)果集
????????while?(resultSet.next()){
????????????Object?o?=?resultTypeClass.newInstance();
????????????//元數(shù)據(jù)
????????????ResultSetMetaData?metaData?=?resultSet.getMetaData();
????????????//metaData.getColumnCount()?:查詢結(jié)果的總列數(shù)
????????????for?(int?i?=?1;?i?<=?metaData.getColumnCount();?i++)?{
????????????????//字段名
????????????????String?columnName?=?metaData.getColumnName(i);
????????????????//字段的值
????????????????Object?value?=?resultSet.getObject(columnName);
????????????????//使用反射或者內(nèi)省,根據(jù)數(shù)據(jù)庫(kù)表和實(shí)體的對(duì)應(yīng)關(guān)系,完成封裝
????????????????//PropertyDescriptor?內(nèi)省庫(kù)中的一個(gè)類,就是把resultTypeClass中的columnName屬性來(lái)生產(chǎn)讀寫(xiě)方法
????????????????PropertyDescriptor?propertyDescriptor?=?new?PropertyDescriptor(columnName,?resultTypeClass);
????????????????Method?writeMethod?=?propertyDescriptor.getWriteMethod();
????????????????//把具體的值封裝到o這個(gè)對(duì)象中
????????????????writeMethod.invoke(o,value);
????????????}
????????????objects.add(o);
????????}
????????return?(List)?objects;
????}
????private?Class>?getClassType(String?paramterType)?throws?ClassNotFoundException?{
????????if?(paramterType!=null){
????????????Class>?aClass?=?Class.forName(paramterType);
????????????return?aClass;
????????}
????????return?null;
????}
????/**
?????*?完成對(duì)#{}解析工作:
?????*?1.?將#{}使用?進(jìn)行替換
?????*?2.?解析出#{}里面的值進(jìn)行存儲(chǔ)
?????*?@param?sql
?????*?@return
?????*/
????private?BoundSql?getBoundSql(String?sql)?{
????????//標(biāo)記處理類:配置標(biāo)記解析器來(lái)完成對(duì)占位符的處理工作
????????ParameterMappingTokenHandler?parameterMappingTokenHandler?=?new?ParameterMappingTokenHandler();
????????GenericTokenParser?genericTokenParser?=?new?GenericTokenParser("#{",?"}",?parameterMappingTokenHandler);
????????//返回解析后的sql
????????String?parseSql?=?genericTokenParser.parse(sql);
????????//#{}里面解析出來(lái)的參數(shù)名稱
????????List?parameterMappings?=?parameterMappingTokenHandler.getParameterMappings();
????????BoundSql?boundSql?=?new?BoundSql(parseSql,parameterMappings);
????????return?boundSql;
????}
}
package?com.dxh.config;
import?com.dxh.utils.ParameterMapping;
import?java.util.ArrayList;
import?java.util.List;
/**
*?該方法的作用下面講解
*/
public?class?BoundSql?{
????private?String?sqlText;//解析后的sql
????private?List?parameterMappingList?=?new?ArrayList<>();
????public?BoundSql(String?sqlText,?List?parameterMappingList) ?{
????????this.sqlText?=?sqlText;
????????this.parameterMappingList?=?parameterMappingList;
????}
}
這里的實(shí)現(xiàn)大致可分為6部分:
注冊(cè)驅(qū)動(dòng),獲取鏈接:通過(guò)傳入的configuration得到datasource,然后調(diào)用 getConnection()得到鏈接獲取SQL語(yǔ)句 我們mapper.xml的SQL語(yǔ)句是這樣的 select * from user where id = #{id} and username = #{username},需要轉(zhuǎn)換為select * from user where id = ? and username =?這樣JDBC才能認(rèn)。同時(shí)我們需要把#{}中的參數(shù)賦值到?這個(gè)占位符處。這里我們定義了一個(gè)getBoundSql方法,通過(guò)標(biāo)記處理類(配置標(biāo)記解析器來(lái)完成對(duì)占位符的處理工作)解析成帶有?的sql,同時(shí)把#{}里面的內(nèi)容傳入ParameterMapping中。通過(guò) connection.prepareStatement(boundSql.getSqlText())得到預(yù)處理對(duì)象設(shè)置參數(shù),我們?cè)趍apper.xml文件中已經(jīng)寫(xiě)了 paramterType,有了入?yún)㈩愋偷娜窂轿覀兛梢酝ㄟ^(guò)反射獲取其對(duì)象。根據(jù)ParameterMapping中存入的的#{}中的內(nèi)容,通過(guò)反射獲取其值,然后與下標(biāo)綁定。執(zhí)行SQL 封裝返回結(jié)果集 這里使用內(nèi)省 返回 (List) objects
7.結(jié)束
此時(shí)我們框架中的代碼已經(jīng)寫(xiě)完了。
8.測(cè)試類
package?com.dxh.test;
import?com.dxh.io.Resource;
import?com.dxh.pojo.User;
import?com.dxh.sqlSession.SqlSession;
import?com.dxh.sqlSession.SqlSessionFacetory;
import?com.dxh.sqlSession.SqlSessionFacetoryBuild;
import?org.dom4j.DocumentException;
import?org.junit.Test;
import?java.beans.PropertyVetoException;
import?java.io.InputStream;
import?java.util.List;
public?class?IPersistenceTest?{
????@Test
????public?void?test()?throws?Exception?{
????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
????????User?user?=?new?User();
????????user.setId(1);
????????user.setUsername("lucy");
????????User?user2?=?sqlSession.selectOne("user.selectOne",user);
????????System.out.println(user2.toString());
//????????List?userList?=?sqlSession.selectList("user.selectList");
//????????for?(User?user1?:?userList)?{
//????????????System.out.println(user1);
//????????}
????}
}
執(zhí)行結(jié)果:
User{id=1,?username='lucy'}
最終的目錄結(jié)構(gòu):
5. 自定義持久層框架的優(yōu)化
我們的自定義持久層框架已經(jīng)完成了,下面我們分析下這個(gè)框架,看看還有沒(méi)有明顯的弊端。
首先,我們先模仿正常的項(xiàng)目,創(chuàng)建一個(gè)Dao層
package?com.dxh.dao;
import?com.dxh.pojo.User;
import?java.util.List;
public?interface?IUserDao?{
????//查詢所有用戶
????public?List?findAll()?throws?Exception ;
????//根據(jù)條件進(jìn)行查詢
????public?User?findByCondition(User?user)?throws?Exception;
}
package?com.dxh.dao;
import?com.dxh.io.Resource;
import?com.dxh.pojo.User;
import?com.dxh.sqlSession.SqlSession;
import?com.dxh.sqlSession.SqlSessionFacetory;
import?com.dxh.sqlSession.SqlSessionFacetoryBuild;
import?java.io.InputStream;
import?java.util.List;
public?class?IUserDaoImpl?implements?IUserDao?{
????@Override
????public?List?findAll()?throws?Exception? {
????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
????????List?userList?=?sqlSession.selectList("user.selectList");
????????return?userList;
????}
????@Override
????public?User?findByCondition(User?user)?throws?Exception?{
????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
????????User?user2?=?sqlSession.selectOne("user.selectOne",user);
????????return?user2;
????}
}
問(wèn)題分析:
很明顯存在代碼重復(fù)的問(wèn)題,他們的前三句話都一樣(加載配置文件、創(chuàng)建SqlSessionFacetory、生產(chǎn)SqlSeesion)
?InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
?SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
?SqlSession?sqlSession?=?sqlSessionFacetory.openSession();statementId存在硬編碼問(wèn)題?List?userList?=?sqlSession.selectList("user.selectList");
?
?User?user2?=?sqlSession.selectOne("user.selectOne",user);
解決思路:
使用代理模式生成Dao層代理實(shí)現(xiàn)類。
在SqlSession接口中增加一個(gè)方法并實(shí)現(xiàn):
//為Dao接口生產(chǎn)代理實(shí)現(xiàn)類
public??T?getMapper(Class>?mapperClass);
????@Override
????public??T?getMapper(Class>?mapperClass)?{
????????//使用JDK動(dòng)態(tài)代理來(lái)為Dao接口生成代理對(duì)象,并返回
????????Object?o?=?Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),?new?Class[]{mapperClass},?new?InvocationHandler()?{
????????????@Override
????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????????????return?null;
????????????}
????????});
????????return?(T)?o;
????}
我們使用Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)方法來(lái)生產(chǎn)代理對(duì)象。一會(huì)我們?cè)賮?lái)實(shí)現(xiàn)invoke方法。
那么此時(shí)我們?nèi)绻傧雸?zhí)行方法應(yīng)該這樣做:
IUserDao?iUserDao?=?sqlSession.getMapper(IUserDao.class);
List?all?=?iUserDao.findAll();
通過(guò) sqlSession.getMapper()方法獲得代理對(duì)象通過(guò)代理對(duì)象調(diào)用 findAll()方法執(zhí)行invoke方法
我們來(lái)看看invoke方法:
Object proxy :當(dāng)前代理對(duì)象的引用 Method method :當(dāng)前被調(diào)用方法的引用 比如我們當(dāng)前的代理對(duì)象 iUserDao調(diào)用的是findAll()方法,而method就是findAll方法的引用Object[] args :傳遞的參數(shù),比如我們想要根據(jù)條件查詢
編寫(xiě)invoke()方法:
我們要首先明確一點(diǎn),不論如何封裝,底層都還是執(zhí)行JDBC代碼,那么我們就要根據(jù)不同情況 調(diào)用selectList或者selectOne。
此時(shí)就有一個(gè)疑問(wèn)了:selectList和selectOne都需要一個(gè)參數(shù)——statementId,而此時(shí)我們是拿不到statementId的。
但是我們可以根據(jù)method對(duì)象得到方法名,和方法所在類的全類名。
因此我們需要規(guī)范下statementId的組成:
“statementId = namespace.id = 方法所在類的全類名.方法名
”
修改UserMapper.xml
????@Override
????public??T?getMapper(Class>?mapperClass)?{
????????//使用JDK動(dòng)態(tài)代理來(lái)為Dao接口生成代理對(duì)象,并返回
????????Object?o?=?Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
????????????????new?Class[]{mapperClass},?new?InvocationHandler()?{
????????????@Override
????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????????????//底層都還是執(zhí)行JDBC代碼??//根據(jù)不同情況?調(diào)用selectList或者selectOne
????????????????//準(zhǔn)備參數(shù):1. statementId
????????????????/**
?????????????????*?**此時(shí)就有一個(gè)疑問(wèn)了:`selectList`和`selectOne`都需要一個(gè)參數(shù)——`statementId`,
?????????????????*?而此時(shí)我們是拿不到`statementId`的。
?????????????????*?但是我們可以根據(jù)`method`對(duì)象得到方法名,和方法所在類的全類名。
?????????????????*?因此我們需要規(guī)范下statementId的組成:
?????????????????*?**statementId??=??namespace.id??=??方法所在類的全類名.方法名
?????????????????*/
????????????????String?methodName?=?method.getName();
????????????????String?className?=?method.getDeclaringClass().getName();
????????????????String?statementId?=?className+"."+methodName;
????????????????//準(zhǔn)備參數(shù):2. args
????????????????//獲取被調(diào)用方法的返回值類型
????????????????Type?genericReturnType?=?method.getGenericReturnType();
????????????????//判斷是否進(jìn)行了泛型類型參數(shù)化?就是判斷當(dāng)前的返回值類型是否有泛型
????????????????if?(genericReturnType?instanceof?ParameterizedType){
????????????????????List?selectList?=?selectList(statementId,?args);
????????????????????return?selectList;
????????????????}
????????????????return?selectOne(statementId,args);
????????????}
????????});
????????return?(T)?o;
????}
測(cè)試:
package?com.dxh.test;
import?com.dxh.dao.IUserDao;
import?com.dxh.io.Resource;
import?com.dxh.pojo.User;
import?com.dxh.sqlSession.SqlSession;
import?com.dxh.sqlSession.SqlSessionFacetory;
import?com.dxh.sqlSession.SqlSessionFacetoryBuild;
import?org.dom4j.DocumentException;
import?org.junit.Test;
import?java.beans.PropertyVetoException;
import?java.io.InputStream;
import?java.util.List;
public?class?IPersistenceTest?{
????@Test
????public?void?test()?throws?Exception?{
????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
????????IUserDao?iUserDao?=?sqlSession.getMapper(IUserDao.class);
????????List?all?=?iUserDao.findAll();
????????System.out.println(all);
????????//打印結(jié)果:[User{id=1, username='lucy'}, User{id=2, username='李四'}, User{id=3, username='null'}]
????????User?user1?=?iUserDao.findByCondition(user);
????????System.out.println(user1);
???????//User{id=1,?username='lucy'}
????}
}
來(lái)源:https://www.cnblogs.com/isdxh/p/13953368.html
作者:DXH's Blog
最近熬夜給大家準(zhǔn)備了515套Java代碼,有一些是業(yè)務(wù)類的小項(xiàng)目,比如Java博客項(xiàng)目,也有腳手架、也有平時(shí)用一些的工具類、21套小程序代碼,也有一些游戲類的項(xiàng)目。
掃以下二維碼并回復(fù)“828”即可獲取
或者在本公眾號(hào)對(duì)話框回復(fù)【828】馬上獲取




