设计思路 使用者
引入自定义的框架jar
编写配置文件
dbconfig.xml:数据库配置信息,存放mapper.xml位置
mapper.xml:sql配置信息
框架
加载配置文件:以字节流存储在内存中
创建Resources类:InputStrean getResourceAsStream(String path)
创建两个Bean:容器对象,存放配置文件解析结果
Configuration:核心配置类,dbconfig.xml解析结果
MapperStatement:映射配置类,mapper.xml解析结果
解析配置文件:dom4j
创建SqlSessionFactoryBuilder:build(InputStream in)
使用dom4j解析配置文件,将结果存放在容器对象
创建SqlSessionFactory对象:生产SqlSession会话对象(工厂模式)
创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
openSession():生产sqlSession
创建SqlSession接口及实现类DefaultSqlSession
定义crud操作
selectList()
selectOne()
update()
delete()
创建Executor接口及实现类SimpleExecutor
query(Configuration, MapperStatement, Object… params):执行JDBC代码
实现 测试项目创建
在resources目录下新建dbconfig.xml配置文件,填写数据源信息
1 2 3 4 5 6 7 8 9 <configuration > <dataSource > <property name ="driver-class" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql:localhost:3306/mydb" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource > </configuration >
1 2 3 4 5 6 7 8 9 10 11 12 <mapper namespace ="user" > <select id ="findAll" resultType ="com.chaofan.pojo.User" > select * from user </select > <select id ="findOne" paramType ="com.chaofan.pojo.User" resultType ="com.chaofan.pojo.User" > select * from user where id=#{id} and username=#{username} </select > </mapper >
使用namespace来区分不同的mapper.xml文件
在父标签内部,按照sql的不同,选择不同的标签来区别,再通过id属性来区分每一个标签,然后按照namespace.id组成每一个sql的唯一标识
最后在dbconfig.xml中指定mapper.xml的位置
1 <mappers resource ="UserMapper.xml" />
使用端暂时结束,然后开始编写库
目前配置文件总览
1 2 3 4 5 6 7 8 9 10 11 <configuration > <dataSource > <property name ="driver-class" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql:localhost:3306/mydb" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource > <mappers resource ="UserMapper.xml" /> </configuration >
1 2 3 4 5 6 7 8 9 10 11 12 13 <mapper namespace ="user" > <select id ="findAll" resultType ="com.chaofan.pojo.User" > select * from user </select > <select id ="findOne" paramType ="com.chaofan.pojo.User" resultType ="com.chaofan.pojo.User" > select * from user where id=#{id} and username=#{username} </select > </mapper >
自定义框架的实现 创建项目 新建一个IPersistence模块
新建Resources类 编写Resources类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.chaofan.io;import java.io.InputStream;public class Resources { public static InputStream getResourceAsStream (String path) { return Resources.class.getClassLoader().getResourceAsStream(path); } }
非常简单,获得当前类的类加载器然后加载配置文件获得输入流
容器对象定义 Configuration核心配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.chaofan.pojo;import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;public class Configuration { private DataSource dataSource; Map<String, MappedStatement> mappedStatementMap = new HashMap <>(); 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; } }
MappedStatement解析出来的Mapper对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.chaofan.pojo;public class MappedStatement { private String id; private String resultType; private String paramType; 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 getParamType () { return paramType; } public void setParamType (String paramType) { this .paramType = paramType; } public String getSql () { return sql; } public void setSql (String sql) { this .sql = sql; } }
编写配置文件解析类 解析配置文件。保存在Configuration类的实例中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.chaofan.config;import com.chaofan.io.Resources;import com.chaofan.pojo.Configuration;import com.mchange.v2.c3p0.ComboPooledDataSource;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.InputStream;import java.util.List;import java.util.Properties;public class XMLConfigBuilder { private final Configuration configuration; public XMLConfigBuilder () { this .configuration = new Configuration (); } @SuppressWarnings("unchecked") public Configuration parseConfig (InputStream in) throws Exception { Document document = new SAXReader ().read(in); 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 dataSource = new ComboPooledDataSource (); dataSource.setJdbcUrl(properties.getProperty("url" )); dataSource.setDriverClass(properties.getProperty("driver-class" )); dataSource.setUser(properties.getProperty("username" )); dataSource.setPassword(properties.getProperty("password" )); configuration.setDataSource(dataSource); List<Element> mapperList = rootElement.selectNodes("//mapper" ); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource" ); InputStream mapperStream = Resources.getResourceAsStream(mapperPath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (configuration); xmlMapperBuilder.parse(mapperStream); } return configuration; } }
使用xpath获取所有的<property>标签,然后遍历获取到的List,保存在Properties类的对象中
也可以用Map存储
Mapper解析类 解析mapper.xml的每一个sql语句,封装到MappedStatement对象中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.chaofan.config;import com.chaofan.pojo.Configuration;import com.chaofan.pojo.MappedStatement;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.InputStream;import java.util.List;public class XMLMapperBuilder { private final Configuration configuration; public XMLMapperBuilder (Configuration configuration) { this .configuration = configuration; } @SuppressWarnings("unchecked") public void parse (InputStream inputStream) throws Exception { Document document = new SAXReader ().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace" ); List<Element> selectList = rootElement.selectNodes("//select" ); for (Element element : selectList) { String id = element.attributeValue("id" ); String resultType = element.attributeValue("resultType" ); String paramType = element.attributeValue("paramType" ); String sqlText = element.getTextTrim(); MappedStatement statement = new MappedStatement (); statement.setId(id); statement.setResultType(resultType); statement.setParamType(paramType); statement.setSql(sqlText); String key = namespace + "." + id; configuration.getMappedStatementMap().put(key, statement); } } }
编写SqlSessionFactoryBuilder类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.chaofan.session;import com.chaofan.config.XMLConfigBuilder;import com.chaofan.pojo.Configuration;import java.io.InputStream;public class SqlSessionFactoryBuilder { public SqlSessionFactory build (InputStream in) throws Exception { XMLConfigBuilder builder = new XMLConfigBuilder (); Configuration configuration = builder.parseConfig(in); return new DefaultSqlSessionFactory (configuration); } }
把InputStream传给XMLConfigBuilder类解析成Configuration对象,然后返回DefaultSqlSessionFactory对象
SqlSessionFactory接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.chaofan.session;public interface SqlSessionFactory { SqlSession openSession () ; }
生产SqlSession,然后编写默认实现类
DefaultSqlSessionFactory 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.chaofan.session;import com.chaofan.pojo.Configuration;public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory (Configuration configuration) { this .configuration = configuration; } @Override public SqlSession openSession () { return new DefaultSqlSession (); } }
这个工厂类生产SqlSession对象
SqlSession接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.chaofan.session;import java.util.List;public interface SqlSession { <E> List<E> selectList (String statementId, Object... params) throws Exception; <T> T selectOne (String statementId, Object... params) throws Exception; int update (String statementId, Object params) throws Exception; int save (String statementId, Object params) throws Exception; int delete (String statementId, Object params) throws Exception; void close () ; }
编写SqlSession实现类DefaultSqlSession 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.chaofan.session;import com.chaofan.pojo.Configuration;import com.chaofan.pojo.MappedStatement;import java.util.List;public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private Executor executor; public DefaultSqlSession (Configuration configuration) { this .configuration = configuration; } @Override public <E> List<E> selectList (String statementId, Object... params) throws Exception { executor = new SimpleExecutor (); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return executor.query(configuration, mappedStatement, params); } @Override public <T> T selectOne (String statementId, Object... params) throws Exception { List<T> t = selectList(statementId, params); if (t.size() == 1 ) { return t.get(0 ); } else if (t.size() > 1 ){ throw new RuntimeException ("返回结果过多" ); } throw new RuntimeException ("返回结果为空" ); } @Override public int update (String statementId, Object params) throws Exception { executor = new SimpleExecutor (); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return executor.update(configuration, mappedStatement, params); } @Override public int save (String statementId, Object params) throws Exception { executor = new SimpleExecutor (); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return executor.update(configuration, mappedStatement, params); } @Override public int delete (String statementId, Object params) throws Exception { executor = new SimpleExecutor (); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return executor.update(configuration, mappedStatement, params); } @Override public void close () { try { executor.close(); }catch (Exception ignore){} } }
Executer接口执行sql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.chaofan.session;import com.chaofan.pojo.Configuration;import com.chaofan.pojo.MappedStatement;import java.sql.SQLException;import java.util.List;public interface Executor { <E> List<E> query (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; int update (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; void close () ; }
编写实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 package com.chaofan.session;import com.chaofan.config.BoundSql;import com.chaofan.pojo.Configuration;import com.chaofan.pojo.MappedStatement;import com.chaofan.utils.GenericTokenParser;import com.chaofan.utils.ParameterMapping;import com.chaofan.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;public class SimpleExecutor implements Executor { private PreparedStatement statement; private Connection connection; @Override @SuppressWarnings({"unchecked", "Duplicates"}) public <E> List<E> query (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); statement = connection.prepareStatement(boundSql.getSqlText()); String paramType = mappedStatement.getParamType(); Class<?> type = getClassType(paramType); List<ParameterMapping> mappingList = boundSql.getParameterMappingList(); for (int i = 0 ; i < mappingList.size(); i++) { ParameterMapping parameterMapping = mappingList.get(i); String content = parameterMapping.getContent(); Field field = type.getDeclaredField(content); field.setAccessible(true ); E o = (E)field.get(params[0 ]); statement.setObject(i+1 , o); } ResultSet resultSet = statement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); Object o = resultTypeClass.getDeclaredConstructor().newInstance(); List<Object> objects = new ArrayList <>(); while (resultSet.next()) { ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 0 ; i < metaData.getColumnCount(); i++) { String columnName = metaData.getColumnName(i+1 ); 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; } @Override @SuppressWarnings("Duplicates") public int update (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); statement = connection.prepareStatement(boundSql.getSqlText()); String paramType = mappedStatement.getParamType(); Class<?> type = getClassType(paramType); List<ParameterMapping> mappingList = boundSql.getParameterMappingList(); for (int i = 0 ; i < mappingList.size(); i++) { ParameterMapping parameterMapping = mappingList.get(i); String content = parameterMapping.getContent(); Field field = type.getDeclaredField(content); field.setAccessible(true ); Object o = field.get(params[0 ]); statement.setObject(i+1 , o); } return statement.executeUpdate(); } @Override public void close () { try { statement.close(); connection.close(); } catch (SQLException ignored) {} } private Class<?> getClassType(String paramType) throws ClassNotFoundException { if (paramType != null ) { return Class.forName(paramType); } throw new NullPointerException ("paramType is null" ); } private BoundSql getBoundSql (String sql) { ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler (); GenericTokenParser genericTokenParser = new GenericTokenParser ("#{" , "}" , tokenHandler); String parseSql = genericTokenParser.parse(sql); List<ParameterMapping> mappings = tokenHandler.getParameterMappings(); return new BoundSql (parseSql, mappings); } }
最后测试一下 1 2 3 4 5 6 7 8 9 InputStream inputStream = Resources.getResourceAsStream("dbconfig.xml" );SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();User user = new User ();user.setId("1" ); user.setName("张三" ); User result = sqlSession.selectOne("user.findOne" , user);System.out.println(result);
补充 根据接口动态生成实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override @SuppressWarnings("unchecked") public <T> T getMapper (Class<T> mapperClass) { Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class []{mapperClass}, (proxy, method, args) -> { String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className + "." + methodName; Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof ParameterizedType) { return selectList(statementId, args); } return selectOne(statementId, args); }); return (T) o; }