源碼學(xué)習(xí)之路----mybatis
在開始本文之前呢,我們首先需要了解一下傳統(tǒng)的jdbc存在的問題
傳統(tǒng)的jdbc的代碼是這樣的:
代碼 1
public class Test { public static final String URL = "jdbc:mysql://localhost:3306/test"; public static final String USER = "root"; public static final String PASSWORD = "root"; public static void main(String[] args) throws Exception { //1.加載驅(qū)動(dòng)程序 Class.forName("com.mysql.jdbc.Driver"); //2. 獲得數(shù)據(jù)庫連接 Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); //3.操作數(shù)據(jù)庫,實(shí)現(xiàn)增刪改查 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess"); //如果有數(shù)據(jù),rs.next()返回true while(rs.next()){ System.out.println(rs.getString("user_name")+" 年齡:"+rs.getInt("age")); } }
觀察如上代碼發(fā)現(xiàn) ,傳統(tǒng)的jdbc存在如下問題:
1)數(shù)據(jù)庫配置信息,以及sql語句的編寫,我們都放入配置文件中,由此以來,方便維護(hù)。
<configuration> <!--數(shù)據(jù)庫配置信息--> <dataSource> <property name="dirverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </dataSource> <!-- 為了一次性將所有的資源全部讀入,此處我們用來存放mapper.xml全路徑--> <mapper resource="UserMapper.xml"></mapper> </configuration>
代碼 3
mapper.xml
說明:
為了防止我們?cè)谡{(diào)用方法時(shí),不同類中方法名重復(fù),我們?cè)O(shè)置 sql的唯一標(biāo)識(shí)namespace.id =》statementId 來避免這個(gè)問題,
比如 UserMapper.class中與Order.class中都存在findAll方法,我們就要在userMapper.xml和OrderMapper.xml中都設(shè)置一下namespace屬性,
這樣 mapper標(biāo)簽的namespace屬性加上select標(biāo)簽的id屬性就可以唯一的確定一個(gè)sql語句了。
<mapper namespace="user"> <!-- 為了防止我們?cè)谡{(diào)用方法時(shí),不同類中方法名重復(fù),我們?cè)O(shè)置 sql的唯一標(biāo)識(shí)namespace.id =》statementId 來避免這個(gè)問題
比如 UserMapper.class中與Order.class中都存在findAll方法,我們就要在userMapper.xml和OrderMapper.xml中都設(shè)置一下namespace屬性,
這樣 mapper標(biāo)簽的namespace屬性加上select標(biāo)簽的id屬性就可以唯一的確定一個(gè)sql語句了--> <select id="findAll" resultType="com.hg.pojo.User"> select * from user </select> <select id="findByCondition" resultType="com.hg.pojo.User" paramterType="com.hg.pojo.User"> select * from user where id = #{id} and username = #{username} </select> </mapper>
這樣,配置文件就寫好了。
接下來,我們分析下框架中需要的類,以及實(shí)現(xiàn)方法:
首先,我們需要加載使用者的配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)在內(nèi)存中。
于是,我們創(chuàng)建一個(gè)Resource類來實(shí)現(xiàn)這個(gè)功能
我們創(chuàng)建一個(gè)maven工程,創(chuàng)建一個(gè)包c(diǎn)om.xx.io來存放我們的Resource類,該類只有一個(gè)方法,就是讀取配置信息成字節(jié)流到內(nèi)存中。
引入依賴
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.17</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency>
代碼 4
public class Resource { //根據(jù)配置文件路徑,將配置文件ji加載成字節(jié)流,存儲(chǔ)在內(nèi)存中 public static InputStream getResourceAsStream(String path){ InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path); return resourceAsStream; } }
然后我們需要?jiǎng)?chuàng)建兩個(gè)Bean來存放解析出來的數(shù)據(jù)庫配置信息和sql配置信息:
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class Configuration { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Map<String, MappedStatement> getMappedStatementMap() { return mappedStatementMap; } public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) { this.mappedStatementMap = mappedStatementMap; } //key statementId value:封裝好的 mappedStatement Map<String,MappedStatement> mappedStatementMap = new HashMap<>(); }
public class MappedStatement {
//id標(biāo)識(shí)
private String id;
// 返回值類型
private String resultType;
// 參數(shù)值類型
private String paramterType;
// sql語句
private String sql;
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 getParamterType() { return paramterType; } public void setParamterType(String paramterType) { this.paramterType = paramterType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } }
MappedStatement類中,我們定義了四個(gè)屬性,分別對(duì)應(yīng)我們將來解析出來的mapper.xml文件中的id、resultType、paramterType和sql。
Configuration類中,我們?cè)O(shè)置兩個(gè)屬性,分別是dataSource和mappedStatementMap ,dataSource屬性存放的是數(shù)據(jù)庫的配置信息,mappedStatementMap存放的是sql的信息,這里可以看到,我們的mappedStatementMap是一個(gè)map類型,因?yàn)槲覀兠總€(gè)mapper.xml中都會(huì)存在多個(gè)sql語句,所以,我們?cè)诮馕鰰r(shí),將會(huì)把我們前面提到的用來唯一定位一條sql語句的statementid來當(dāng)做map的key,而解析出來的MappedStatement就是map的value。
至此,我們就把兩個(gè)配置文件解析好后存放配置信息的類都創(chuàng)建好了,接下來,我們來看一下,這個(gè)配置文件是如何解析的呢?
public interface SqlSession { // 查詢所有 public <E> List<E> selectList(String statementId,Object... params) throws Exception; // 根據(jù)條件查詢一條 public <T> T selectOne(String statementId,Object... params) throws Exception; public int insert(String statementId,Object... params) throws Exception; public int delete(String statementId,Object... params) throws Exception; public int update(String statementId,Object... params) throws Exception; //為dao接口生成代理實(shí)現(xiàn)類 public <T> T getMapper(Class<?> mapperClass); }
public interface SqlSessionFactory { public SqlSession openSession(); }
import com.hg.config.XMLConfigBuilder; import com.hg.pojo.Configuration; import org.dom4j.DocumentException; import java.beans.PropertyVetoException; import java.io.InputStream; public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException { // 第一:使用dom4j解析配置文件,將解析出來的內(nèi)容封裝到Configuration中 XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(in); // 第二:創(chuàng)建sqlSessionFactory對(duì)象: 工廠類:生產(chǎn)sqlSession:會(huì)話對(duì)象 DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }
import com.hg.io.Resource; import com.hg.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 XMLConfigBuilder { private Configuration configuration; public XMLConfigBuilder() { this.configuration = new Configuration(); } //該方法是使用dom4j將配置文件踐行解析,封裝Configuration public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException { Document document = new SAXReader().read(inputStream); //拿到 <configuration> Element rootElement = document.getRootElement(); List<Element> 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); } ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(properties.getProperty("dirverClass")); comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); comboPooledDataSource.setUser(properties.getProperty("username")); comboPooledDataSource.setPassword(properties.getProperty("password")); configuration.setDataSource(comboPooledDataSource); // mapper.xml解析 拿到路徑 獲取字節(jié)輸入流 進(jìn)行解析 List<Element> mapperList = rootElement.selectNodes("http://mapper"); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource"); InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); xmlMapperBuilder.parse(resourceAsStream); } return configuration; } }
這邊我們?cè)敿?xì)講解一下這個(gè)類:
首先我們創(chuàng)建一個(gè)午餐的構(gòu)造方法來初始化一個(gè)Configuration類用來存儲(chǔ)接下來解析的所有配置信息。
上面我們提到為了一次性將所有的資源全部讀入我們將mapper.xml文件的的全路徑也配置到了sqlMapConfig.xml中,接下來我們將進(jìn)行mapper.xml 的解析
我們通過rootElement.selectNodes("http://mapper");來獲取到配置信息中的所有mapper標(biāo)簽,上文代碼中層提到,里面包含著所有的mapper.xml 的路徑信息(因?yàn)槲覀兛赡軙?huì)有多個(gè)mapper文件)。
接下來我們通過循環(huán)遍歷element.attributeValue("resource")獲取到每一個(gè)mapper.xml的路徑,并使用Resource類進(jìn)行字節(jié)流的讀取。
然后我們需要?jiǎng)?chuàng)建一個(gè)XMLMapperBuilder類來對(duì)每個(gè)mapper.xml進(jìn)行處理。我們將Configuration作為參數(shù)傳遞進(jìn)去是為了將解析后的mapper.xml信息存儲(chǔ)到Configuration里面的mappedStatementMap集合中。
然后我們調(diào)用XMLMapperBuilder的parse方法來處理讀取到的mapper.xml文件字節(jié)流。
好了,接下來我們開始研究下如何將mapper.xml文件的信息解析出來。
我們先創(chuàng)建XMLMapperBuilder類
代碼 9
package com.hg.config; import com.hg.pojo.Configuration; import com.hg.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 XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(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<Element> 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); } } }
上面解析sqlMapConfig.xml 的代碼中我們?cè)岬?,?Configuration 作為參數(shù)傳遞給 XMLMapperBuilder ,那么 XMLMapperBuilder 代碼中,我們創(chuàng)建了一個(gè)有參的構(gòu)造方法來進(jìn)行參數(shù)的傳遞,接下來我們創(chuàng)建一個(gè)parse方法來進(jìn)行mapper.xml文件的解析,這里我們接收到的參數(shù)是 XMLConfigBuilder 中傳遞過來的mapper.xml字節(jié)流,同樣的使用dom4j來進(jìn)行解析,重復(fù)的代碼我不再贅述,這邊解析的是mapper.xml中所有select(insert,update,delete)標(biāo)簽下的所有屬性,包括id,resultType,paramterType以及sql(后面我們還要在MappedStatement中加入一個(gè)標(biāo)記屬性,來判斷我們這個(gè)MappedStatement是增,刪,改,查中的哪種);
上文我們提到要想準(zhǔn)確的定位一個(gè)sql需要通過 statementid(namespace.id)來進(jìn)行唯一標(biāo)示,所以這邊使用 statementid 作為 mappedStatementMap 的key,而獲取到的select標(biāo)簽下的所有信息封裝成 MappedStatement 作為mappedStatementMap 的value,這樣這個(gè)parse方法就把mapper.xml中的配置信息傳遞到了 Configuration 中的 mappedStatementMap 里。最終 XMLConfigBuilder 中的parseConfig方法將所有配置信息都讀取到了Configuration中并將Configuration返回。這樣上文中的 代碼7 SQLSessionFactoryBuilder 類的第一部分就完成了,有了配置信息,接下來,我們繼續(xù)探討如何來進(jìn)行 SQLSession 的創(chuàng)建以及如何進(jìn)行查詢,對(duì)查詢結(jié)果的解析以及封裝。
我們創(chuàng)建一個(gè)DefaultSqlSessionFactory來實(shí)現(xiàn)SqlSessionFactory接口
代碼 10
public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); } }
此時(shí),我們要?jiǎng)?chuàng)建一個(gè)構(gòu)造函數(shù)來傳遞我們剛才解析出來的Configuration,然后我們?cè)賱?chuàng)建一個(gè)DefaultSqlSession類來實(shí)現(xiàn)SqlSession接口的方法,同使這個(gè)DefaultSqlSession類也要?jiǎng)?chuàng)建一個(gè)有參的構(gòu)造方法用來傳遞我們的Configuration參數(shù)(上面我們分析過,這個(gè)Configuration參數(shù)很重要,數(shù)據(jù)庫的配置信息以及sql的信息全都存儲(chǔ)在這個(gè)Configuration中),然后我們?cè)贒efaultSqlSessionFactory類的openSession方法中創(chuàng)建這個(gè)DefaultSqlSession實(shí)例對(duì)象。
接下來我們一起研究一下這個(gè)DefaultSqlSession都完成了什么功能:
代碼 11
public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } @Override public <E> List<E> selectList(String statementId, Object... params) throws Exception { // 將要完成對(duì)simpleExcutor里的query方法 SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List<Object> list = simpleExecutor.query(configuration, mappedStatement, params); return (List<E>) list; } @Override public <T> T selectOne(String statementId, Object... params) throws Exception { List<Object> objects = selectList(statementId, params); if(objects.size()==1){ return (T) objects.get(0); }else{ throw new RuntimeException("查詢結(jié)果為空或返回結(jié)果過多"); } } @Override public int insert(String statementId, Object... params) throws Exception { return this.update(statementId,params); } @Override public int delete(String statementId, Object... params) throws Exception { return this.update(statementId,params); } @Override public int update(String statementId, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); int rows = simpleExecutor.update(configuration, mappedStatement, params); return rows; } @Override public <T> T getMapper(Class<?> mapperClass) { //使用動(dòng)態(tài)代理為DAO接口生成代理對(duì)象;并返回 Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> { // 底層就是執(zhí)行jdbc 根據(jù)不同情況,調(diào)用 selectList和selectOne // 準(zhǔn)備參數(shù) 1statementid sql的唯一標(biāo)識(shí) namespace.id=接口權(quán)限定名.方法名 System.out.println(DefaultSqlSession.class.getClassLoader()); System.out.println(mapperClass.getClassLoader()); String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className+"."+methodName; MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); switch (sqlCommandType){ case SELECT: //準(zhǔn)備參數(shù)2:params:args //獲取被調(diào)用方法的返回值類型 Type genericReturnType = method.getGenericReturnType(); //判斷是否進(jìn)行了泛型類型參數(shù)化 if(genericReturnType instanceof ParameterizedType){ List<Object> objects = selectList(statementId, args); return objects; } return selectOne(statementId,args); case DELETE: return delete(statementId,args); case INSERT: return insert(statementId,args); case UPDATE: return update(statementId,args); default: return null; } }); return (T) proxyInstance; } }
我們實(shí)現(xiàn)了SqlSession接口中的所有方法,接下來我們逐步介紹這個(gè)類:
可以看到,我們的增刪改查方法中都用到一個(gè)SimpleExecutor類,這個(gè)類就是用來實(shí)現(xiàn)我們的核心功能:
1.數(shù)據(jù)庫的連接
2.獲取sql語句并進(jìn)行轉(zhuǎn)換(把我們寫的sql語句中的#{}替換成?)
3.獲取預(yù)處理對(duì)象
4.設(shè)置參數(shù)(將我們的傳值設(shè)置到預(yù)處理對(duì)象中)
5.執(zhí)行sql
6.封裝返回結(jié)果(將查詢到的數(shù)據(jù)庫數(shù)據(jù)封裝成我們的pojo對(duì)象)
接下來我們將一步一步完成這些功能。
我們創(chuàng)建一個(gè)執(zhí)行器接口Executor,這里我們提供兩個(gè)方法,query和update,query我們用來查數(shù)據(jù),update我們用來 增 刪 改 數(shù)據(jù)。
代碼 12
public interface Executor { public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; }
然后我們創(chuàng)建一個(gè)SimpleExecutor類來實(shí)現(xiàn)這個(gè)接口中的方法,后面我們將重點(diǎn)的分析一下查詢方法的實(shí)現(xiàn)
代碼 13
public class SimpleExecutor implements Executor { @Override public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { //1注冊(cè)驅(qū)動(dòng),獲取連接 Connection connection = configuration.getDataSource().getConnection(); //2獲取sql語句 轉(zhuǎn)換sql語句 #{} 轉(zhuǎn)換成 ? 轉(zhuǎn)換的過程對(duì)#{}的值解析出來并存儲(chǔ) String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); //3獲取預(yù)處理對(duì)象 PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //4設(shè)置參數(shù) // 獲取到參數(shù)的全路徑 String paramterType = mappedStatement.getParamterType(); Class<?> paramterTypeClass = getClassType(paramterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = paramterTypeClass.getDeclaredField(content); // 設(shè)置暴力訪問 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); } //5執(zhí)行sql ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); ArrayList<Object> objects = new ArrayList<>(); //6封裝返回結(jié)果集 while(resultSet.next()){ Object o = resultTypeClass.newInstance(); //元數(shù)據(jù) ResultSetMetaData metaData = resultSet.getMetaData(); // getColumnCount 獲取總列數(shù) 就是屬性的個(gè)數(shù) for (int i = 1; i <= metaData.getColumnCount(); i++) { //字段名 String columnName = metaData.getColumnName(i); //字段的值 Object value = resultSet.getObject(columnName); //使用反射或者內(nèi)省,根據(jù)數(shù)據(jù)庫表和實(shí)體的對(duì)應(yīng)關(guān)系,完成封裝 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value); } objects.add(o); } return (List<E>) objects; } @Override public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { //1注冊(cè)驅(qū)動(dòng),獲取連接 Connection connection = configuration.getDataSource().getConnection(); //2獲取sql語句 轉(zhuǎn)換sql語句 #{} 轉(zhuǎn)換成 ? 轉(zhuǎn)換的過程對(duì)#{}的值解析出來并存儲(chǔ) String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); //3獲取預(yù)處理對(duì)象 PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //4設(shè)置參數(shù) // 獲取到參數(shù)的全路徑 String paramterType = mappedStatement.getParamterType(); Class<?> paramterTypeClass = getClassType(paramterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = paramterTypeClass.getDeclaredField(content); // 設(shè)置暴力訪問 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); } //5執(zhí)行sql int rows = preparedStatement.executeUpdate(); return rows; } private Class<?> getClassType(String paramterType) throws ClassNotFoundException { if (paramterType!=null){ Class<?> aClass = Class.forName(paramterType); return aClass; } return null; } //對(duì)#{}進(jìn)行解析,將#{}使用?代替,解析#{}中的值進(jìn)行存儲(chǔ) private BoundSql getBoundSql(String sql) { //標(biāo)記處理類:配置標(biāo)記解析器來完成對(duì)占位符的解析處理工作 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); //標(biāo)記解析器 GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); //用標(biāo)記解析器解析出來的sql String parseSql = genericTokenParser.parse(sql); // #{}解析出來的參數(shù)名稱 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(parseSql, parameterMappings); return boundSql; } }
我們具體來分析下這個(gè)query方法,首先我們注冊(cè)驅(qū)動(dòng),獲取數(shù)據(jù)庫連接,然后,根據(jù)從MappedStatement中獲取sql語句,然后通過getBoundSql方法來解析我們的sql,實(shí)際上這一步就是來將我們sql中的#{}替換成?并將#{}中的屬性取出并存儲(chǔ)到parameterMappingList,也就是代碼中g(shù)etBoundSql方法的作用,該方法用到的工具類我后面會(huì)貼出來。
將sql處理好后,我們獲取預(yù)處理對(duì)象,然后開始設(shè)置參數(shù),這里我們將會(huì)用到反射。
首先我們獲取到入?yún)⒌娜窂?,比如com.xx.User,然后我們通過getClassType這個(gè)方法初始化這個(gè)類,然后我們循環(huán)遍歷 parameterMappingList 取出里面的每一個(gè)參數(shù),并通過反射將我們傳遞進(jìn)來的參數(shù)中的值取出來,并對(duì)應(yīng)到?相應(yīng)的位置。而后,我們執(zhí)行查詢操作,獲取到查詢的結(jié)果。
既然獲取到結(jié)果,我們就要根據(jù)我們?cè)O(shè)置的resultType來對(duì)結(jié)果集進(jìn)行封裝,我們同樣根據(jù)反射來取得我們r(jià)esultType中的類,因?yàn)槲覀儾樵兊臅r(shí)候,可能是多條,可能是一條,所以我們創(chuàng)建一個(gè)Object集合來進(jìn)行結(jié)果集的存儲(chǔ)和返回。
這里我們通過while來遍歷結(jié)果集,每一次循環(huán)都是一條數(shù)據(jù),所以我們沒循環(huán)一次結(jié)果集都要?jiǎng)?chuàng)建一個(gè)Object對(duì)象進(jìn)行接收, 每遍歷一次結(jié)果集我們都要取出數(shù)據(jù)源再進(jìn)行每一列,也就是每個(gè)字段的遍歷,此處我們for循環(huán)的 i 是從1開始的,因?yàn)槲覀冊(cè)跀?shù)據(jù)源中取列名是從腳標(biāo)1開始的,我們獲取到列名再通過列名獲取到列值,而后我們通過內(nèi)省來將列的值賦值給我們創(chuàng)建的 Object對(duì)象,每一次for循環(huán)就遍歷賦值一個(gè)列,這樣我們就將每個(gè)結(jié)果集的每一條數(shù)據(jù)都映射給了Object對(duì)象,最終生成一個(gè)集合最終返回給用戶。這樣,我們的Query方法就實(shí)現(xiàn)了。至此,傳統(tǒng)(后面我們還講分析一下傳統(tǒng)方法的不足,以及如何優(yōu)化,代碼中已經(jīng)給出了優(yōu)化的部分)的查詢方法就完成了,我們可以寫測(cè)試類進(jìn)行測(cè)試。我將工具類粘貼在下面,方便我們接下來的測(cè)試。
public class GenericTokenParser { private final String openToken; //開始標(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()方法來實(shí)現(xiàn) */ public String parse(String text) { // 驗(yàn)證參數(shù)問題,如果是null,就返回空字符串。 if (text == null || text.isEmpty()) { return ""; } // 下面繼續(xù)驗(yàn)證是否包含開始標(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) { // 判斷如果開始標(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 < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } } public class ParameterMapping { private String content; public ParameterMapping(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } public class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是參數(shù)名稱 #{id} #{username} public String handleToken(String content) 主站蜘蛛池模板: 青青国产揄拍视频| 国产福利免费在线观看| 线观看的国产成人av天堂| 熟女一区二区中文字幕| 天天躁夜夜躁狠狠喷水| 欧美日韩在线视频| 国产精品无码专区| 久99久热免费视频播放| 精品亚洲没码中文字幕| 免费人成年激情视频在线观看| 一个人免费观看WWW在线视频| 国产成人精品无人区一区| 亚洲精品国产一二三区| 丰满妇女强制高潮18xxxx| 99亚洲男女激情在线观看| 电影在线观看+伦理片| 亚洲av无码之国产精品网址蜜芽 | 亚洲欧美电影在线一区二区| 亚洲精品一区二区三区大桥未久| 人妻系列无码专区69影院| 免费福利视频一区二区三区高清| 欧美日韩一线| 澳门永久av免费网站| 国产无遮挡又黄又爽不要vip软件 国产成人精品一区二区秒拍1o | 中文字幕亚洲综合久久青草| 又黄又爽又色的少妇毛片| 热99久久这里只有精品| 中文字幕v亚洲日本在线电影| 欧美18videosex性欧美tube1080| 丁香五月亚洲综合在线国内自拍| 邵武市| 中文字幕av无码免费一区| 日本道不卡一二三区视频| 久久狠狠高潮亚洲精品夜色| 欧美视频专区一二在线观看| 曰本丰满熟妇xxxx性| 中文成人无字幕乱码精品区| 国产成人一区二区三区在线| 成人国产精品一区二区网站公司 | 日韩人妻精品中文字幕专区| 精品亚洲一区二区三区四区|
