咱們來自定義一個持久層框架,也就是Mybatis的簡易版。java
idea中新建maven工程IPersistence_test:
在resources目錄下新建sqlMapConfig.xml文件,mysql
<Configuration> <dataSource> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false"></property> <property name="user" value="root"></property> <property name="password" value="000"></property> </dataSource> <mapper resource="UserMapper.xml"></mapper> </Configuration>
UserMapper.xml:sql
<mapper namespace="user"> <select id="selectOne" paramterType="com.lagou.pojo.User" resultType="com.lagou.pojo.User"> select * from user where id = #{id} and username =#{username} </select> <select id="selectList" resultType="com.lagou.pojo.User"> select * from user </select> </mapper>
User實體類:數據庫
package com.lagou.test; /** * @author liuyj * @Title: User * @create 2020-05-29 15:06 * @ProjectName lagou_project * @Description: TODO */ public class User { private Integer id; private String username; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + '}'; } }
使用端暫時就搭建完成。express
下面咱們來搭建持久層框架:
新建一個module,maven項目:IPersistence
pom.xml中引入一下依賴數組
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</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> <!-- 解析xml文件--> <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> </dependencies>
建立一個Configuration類,主要是存放從sqlMapConfig.xml和Usermapper.xml配置文件中解析出來的一些元素和內容,用來一層層向下傳遞:mybatis
package com.lagou.pojo; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author liuyj * @Title: Configuration * @create 2020-05-27 15:20 * @ProjectName IPersistence * @Description: 存放sqlMapConfig.xml解析出來的內容 */ public class Configuration { //存放數據庫配置信息,從sqlMapConfig.xml中解析出來 private DataSource dataSource; //存放Mapper.xml中解析出來的內容,key是statementId private Map<String,MappedStatement> mappedStatementMap=new HashMap<String, MappedStatement>(); 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; } }
其中DataSource 封裝的是數據庫信息,Configuration中封裝了一個對象MappedStatement:app
package com.lagou.pojo; /** * @author liuyj * @Title: MappedStatement * @create 2020-05-27 15:13 * @ProjectName IPersistence * @Description: 存放UserMapper.xml解析出來的內容 */ public class MappedStatement { //id標識 private String id; //返回值類型 private String resultType; //傳入參數類型 private String paramenterType; //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 getParamenterType() { return paramenterType; } public void setParamenterType(String paramenterType) { this.paramenterType = paramenterType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } }
主要是用來存儲從映射配置文件中解析出來的sql查詢標籤的id及傳入參數、返回結果類型、查詢的sql等,其中Mapper.xml中每個標籤,
好比:框架
<select id="selectList" resultType="com.lagou.pojo.User"> select * from user </select>
都會封裝成一個MappedStatement對象,而後全部的MappedStatement對象都被存儲到Configuration類中的Map集合mappedStatementMap當中去,Map集合中的key是statementId(statementId由兩部分組成,一是Mapper.xml中的namespace,二是每個標籤中的id,好比UserMapper.xml中的selectOne,在Configuration中map集合中的key值就是user.selectOne)。dom
Resource文件:
主要用來讀取xml文件,做爲一個字節流存儲在內存中:
package com.lagou.io; import java.io.InputStream; /** * @author liuyj * @Title: * @create 2020-05-27 14:48 * @ProjectName IPersistence * @Description: TODO */ public class Resources { //根據配置文件的路徑,將配置文件加載成字節輸入流,存儲在內存中 public static InputStream getResourceAsStream(String path){ InputStream resourceStream= Resources.class.getClassLoader().getResourceAsStream(path); return resourceStream; } }
SqlSessionFactoryBuilder:
package com.lagou.sqlSession; import com.lagou.config.XMLConfigBuilder; import com.lagou.pojo.Configuration; import org.dom4j.DocumentException; import java.beans.PropertyVetoException; import java.io.InputStream; /** * @author liuyj * @Title: SqlSessionFactoryBuilder * @create 2020-05-27 15:37 * @ProjectName IPersistence * @Description: TODO */ public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException { //第一,使用dom4j解析配置文件,將解析出來的內容封裝到configuration中 XMLConfigBuilder xmlConfigBuilder=new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(in); //第二:建立sqlSessionFactory對象:工廠類:生產sqlSession:會話對象 DefaultSqlSessionFactory defaultSqlSessionFactory=new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }
XMLConfigBuilder :
使用dom4j解析sqlMapConfig.xml文件,並調用XMLMapperBuilder 中的方法解析Mapper.xml映射文件,將結果封裝在Configuration對象中:
package com.lagou.config; import com.lagou.io.Resources; import com.lagou.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; /** * @author liuyj * @Title: XMLConfigBuilder * @create 2020-05-27 15:39 * @ProjectName IPersistence * @Description: TODO */ 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("//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("driverClass")); comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); comboPooledDataSource.setUser(properties.getProperty("user")); comboPooledDataSource.setPassword(properties.getProperty("password")); configuration.setDataSource(comboPooledDataSource); //解析sqlMapConfig.xml裏面的mapper標籤, // 解析mapper.xml:拿到路徑--字節輸入流--dom4j解析 List<Element> mapperList = rootElement.selectNodes("//mapper"); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource"); InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath); XMLMapperBuilder xmlMaperBuilder=new XMLMapperBuilder(configuration); xmlMaperBuilder.parse(resourceAsStream); } return configuration; } }
XMLMapperBuilder :
用來解析映射配置文件Mapper.xml中的內容:
package com.lagou.config; import com.lagou.pojo.Configuration; import com.lagou.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; /** * @author liuyj * @Title: XMLMapperBuilder * @create 2020-05-28 10:59 * @ProjectName lagou_project * @Description: 解析mapper.xml文件 */ 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("//select"); for (Element element : list) { String id = element.attributeValue("id"); String parameterType = element.attributeValue("parameterType"); String resultType = element.attributeValue("resultType"); String sqlText = element.getTextTrim(); String key=namespace+"."+id; MappedStatement mappedStatement=new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParamenterType(parameterType); mappedStatement.setResultType(resultType); mappedStatement.setSql(sqlText); configuration.getMappedStatementMap().put(key,mappedStatement); } } }
package com.lagou.sqlSession; /** * @author liuyj * @Title: SqlSessionFactory * @create 2020-05-27 15:38 * @ProjectName IPersistence * @Description: TODO */ public interface SqlSessionFactory { SqlSession openSession(); }
package com.lagou.sqlSession; import com.lagou.pojo.Configuration; /** * @author liuyj * @Title: DefaultSqlSessionFactory * @create 2020-05-28 11:38 * @ProjectName lagou_project * @Description: TODO */ public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } public SqlSession openSession() { return new DefaultSqlSession(configuration); } }
SqlSession及實現類DefaultSqlSession:
package com.lagou.sqlSession; import java.util.List; /** * @author liuyj * @Title: SqlSession * @create 2020-05-28 11:56 * @ProjectName lagou_project * @Description: TODO */ public interface SqlSession { //查詢全部 public <E> List<E> selectList(String statementid, Object... params) throws Exception; //根據條件查詢單個 public <T> T selectOne(String statementid,Object... params) throws Exception; }
package com.lagou.sqlSession; import com.lagou.pojo.Configuration; import com.lagou.pojo.MappedStatement; import java.lang.reflect.*; import java.util.List; /** * @author liuyj * @Title: DefaultSqlSession * @create 2020-05-28 11:57 * @ProjectName lagou_project * @Description: TODO */ public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } public <E> List<E> selectList(String statementid, Object... params) throws Exception { //將要去完成對simpleExecutor裏的query方法的調用 SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid); List<Object> list = simpleExecutor.query(configuration, mappedStatement, params); return (List<E>) list; } 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("查詢結果爲空或者返回結果過多"); } } }
SqlSession實現類中調用的Executor及其實現類SimpleExecutor :
package com.lagou.sqlSession; import com.lagou.pojo.Configuration; import com.lagou.pojo.MappedStatement; import java.util.List; /** * @author lyj * @Title: Executor * @ProjectName lagou_project * @Description: TODO * @date 2020/5/28 22:00 */ public interface Executor { public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; }
package com.lagou.sqlSession; import com.lagou.pojo.Configuration; import com.lagou.pojo.MappedStatement; import com.lagou.utils.GenericTokenParser; import com.lagou.utils.ParameterMapping; import com.lagou.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 lyj * @Title: SimpleExecutor * @ProjectName lagou_project * @Description: TODO * @date 2020/5/28 21:59 */ public class SimpleExecutor implements Executor { public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { //註冊驅動,獲取數據庫鏈接 Connection connection = configuration.getDataSource().getConnection(); //獲取sql語句:select * from user where id=#{id} and username=#{username} //轉換sql:select * from user where id=? and username=?,同時須要對#{}裏面的值進行解析存儲 String sql = mappedStatement.getSql(); BoundSql boundSql=getBoundSql(sql); //獲取預處理對象preparedStatement PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //設置參數 //獲取到了參數的全路徑 String paramenterType = mappedStatement.getParamenterType(); Class<?> parametertypeClass=getClassType(paramenterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = parametertypeClass.getDeclaredField(content); //暴力訪問 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); } //執行sql ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); ArrayList<Object> objects = new ArrayList<Object>(); //封裝返回結果集 while (resultSet.next()) { Object o = resultTypeClass.newInstance(); //元數據 ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { //字段名 String columnName = metaData.getColumnName(i); //字段值 Object value = resultSet.getObject(columnName); //使用反射或者內省,根據數據庫表和實體的對應關係,完成封裝 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value); } objects.add(o); } return (List<E>) objects; } /** * 完成對#{}的解析工做:1.將#{}使用?代替 ,2.解析出#{}裏面的值進行存儲 * @param sql * @return */ private BoundSql getBoundSql(String sql) { //標記處理類:配置標記解析器GenericTokenParser來完成對配置文件的解析工做,其中TokenHandler主要完成處理 ParameterMappingTokenHandler parameterMappingTokenHandler=new ParameterMappingTokenHandler(); //GenericTokenParser:通用的標記解析器,完成了對代碼中佔位符的解析,而後再根據給定的標記處理器(TokenHandler)來進行表達式的處理 //三個參數:分別爲openToken(開始標記)、closeToken(結束標記)、handler(標記處理器) GenericTokenParser genericTokenParser=new GenericTokenParser("#{","}",parameterMappingTokenHandler); //解析出來的sql String parseSql = genericTokenParser.parse(sql); //從#{}裏面解析出來的參數名稱 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql=new BoundSql(parseSql,parameterMappings); return boundSql; } }
package com.lagou.sqlSession; import com.lagou.utils.ParameterMapping; import java.util.ArrayList; import java.util.List; /** * @author lyj * @Title: BoundSql * @ProjectName lagou_project * @Description: TODO * @date 2020/5/28 22:03 */ public class BoundSql { private String sqlText; private List<ParameterMapping>parameterMappingList=new ArrayList<ParameterMapping>(); public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List<ParameterMapping> getParameterMappingList() { return parameterMappingList; } public void setParameterMappingList(List<ParameterMapping> parameterMappingList) { this.parameterMappingList = parameterMappingList; } }
SimpleExecutor 中使用的幾個標記解析器,也附錄一下吧,是從mybatis源碼中直接拿來用的:
package com.lagou.utils; /** * @author Clinton Begin */ public class GenericTokenParser { private final String openToken; //開始標記 private final String closeToken; //結束標記 private final TokenHandler handler; //標記處理器 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } /** * 解析${}和#{} * @param text * @return * 該方法主要實現了配置文件、腳本等片斷中佔位符的解析、處理工做,並返回最終須要的數據。 * 其中,解析工做由該方法完成,處理工做是由處理器handler的handleToken()方法來實現 */ public String parse(String text) { // 驗證參數問題,若是是null,就返回空字符串。 if (text == null || text.length()==0) { return ""; } // 下面繼續驗證是否包含開始標籤,若是不包含,默認不是佔位符,直接原樣返回便可,不然繼續執行。 int start = text.indexOf(openToken, 0); if (start == -1) { return text; } // 把text轉成字符數組src,而且定義默認偏移量offset=0、存儲最終須要返回字符串的變量builder, // text變量中佔位符對應的變量名expression。判斷start是否大於-1(即text中是否存在openToken),若是存在就執行下面代碼 char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; while (start > -1) { // 判斷若是開始標記前若是有轉義字符,就不做爲openToken進行處理,不然繼續處理 if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //重置expression變量,避免空指針或者老數據干擾。 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) {////存在結束標記時 if (end > offset && src[end - 1] == '\\') {//若是結束標記前面有轉義字符時 // 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 {//不存在轉義字符,即須要做爲參數進行處理 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 { //首先根據參數的key(即expression)進行參數處理,返回?做爲佔位符 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(); } }
ParameterMapping:
package com.lagou.utils; 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; } }
ParameterMappingTokenHandler:
package com.lagou.utils; import java.util.ArrayList; import java.util.List; public class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是參數名稱 #{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<ParameterMapping> getParameterMappings() { return parameterMappings; } public void setParameterMappings(List<ParameterMapping> parameterMappings) { this.parameterMappings = parameterMappings; } }
package com.lagou.utils; /** * @author Clinton Begin */ public interface TokenHandler { String handleToken(String content); }
先生成jar包,執行mvn install命令。
mvn install
在使用端IPersistence_test中引入jar包:
<dependency> <groupId>com.lagou</groupId> <artifactId>IPersistence</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
數據庫建表:
測試:
package com.lagou.test; import com.lagou.dao.IUserDao; import com.lagou.io.Resources; import com.lagou.sqlSession.SqlSession; import com.lagou.sqlSession.SqlSessionFactory; import com.lagou.sqlSession.SqlSessionFactoryBuilder; import org.dom4j.DocumentException; import org.junit.Before; import org.junit.Test; import java.beans.PropertyVetoException; import java.io.InputStream; /** * @author liuyj * @Title: IPersistenctTest * @create 2020-05-27 15:08 * @ProjectName IPersistence * @Description: TODO */ public class IPersistenctTest { private SqlSession sqlSession; @Before public void before() throws PropertyVetoException, DocumentException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); sqlSession = sqlSessionFactory.openSession(); } @Test public void test() throws Exception { //調用 User user=new User(); user.setId(1); user.setUsername("張三"); User user2 = sqlSession.selectOne("user.selectOne", user); System.out.println(user2); } }
運行結果:
大功告成。 下節咱們來說對於咱們這個框架的一個優化。