咱们来自定义一个持久层框架,也就是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); } }
运行结果:
大功告成。 下节咱们来说对于咱们这个框架的一个优化。