Mybatis学习【自定义持久层框架】

243 阅读8分钟

自定义持久层框架(参考拉钩Java课程)

分析JDBC操作问题

// 使用原生JDBC代码进行查询
public static void main(String[] args) {
	Connection connection = null;
 	PreparedStatement preparedStatement = null;
 	ResultSet resultSet = null;
 	try {
 		//加载数据库驱动
 		Class.forName("com.mysql.jdbc.Driver");
		//通过驱动管理类获取数据库链接
		connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?				characterEncoding=utf-8","root", "root");
		//定义 sql 语句 ?表示占位符
		String sql = "select * from user where username = ?";
		//获取预处理 statement
		preparedStatement = connection.prepareStatement(sql);
		//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值
		preparedStatement.setString(1, "王五");
		//向数据库发出 sql 执行查询,查询出结果集
		resultSet = preparedStatement.executeQuery();
		//遍历查询结果集
		while(resultSet.next()){
 			System.out.println(resultSet.getString("id")+"
 			"+resultSet.getString("username"));
		}
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		//释放资源
		if(resultSet!=null){
		try {
			resultSet.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	if(preparedStatement!=null){
		try {
			preparedStatement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	if(connection!=null){
	try {
		connection.close();
	} catch (SQLException e) {
	// TODO Auto-generated catch block
		e.printStackTrace();
	}
}
JDBC问题总结:
 1.数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能
 2.Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变java 代码。
 3.使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
 4.对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

问题解决思路:

 1.连接频繁创建->使用连接池
 2.Sql语句与参数在代码中出现硬编码->使用配置文件存储
 3.手动对封装的结果集解析->反射、内省
问题解决思路与设计图片:

自定义框架设计

使用端:
 提供核心配置文件
    SqlMapConfig.xml: 存放数据源相关信息并引入Mapper.xml
    Mapper.xml: 存储sql语句的配置文件信息(包括但不限于sql语句、参数、返回值(基本也就这些))
框架端:
 1.读取配置文件
    文件读取完以流的方式存在,我们不能将文件以流的形式存储在内存中,通过创建JavaBean的方式存储
    (1) Configuration配置类主要用于保存SqlMapConfig.xml文件中读取的xml结点的信息(数据库),以及映射的SQL语句的集合。
    (2) MappedStatement: sql语句、Statement类型、输入参数Java类型、输出参数Java类型
 2.创建 SqlSessionFactroyBuilder类进行解析xml文件
   方法:
        SqlSessionFactory build();
        (1) 使用DOM4J解析配置文件,并将解析出来的数据封装到Configuration和MappedStatement中
        (2) 创建SqlSessionFactory的实现类DefaultSqlSession 注: DefaultSqlSession 类主要用于生成 mapper 代理对象,以及执行 SQL 语句的方法。
 3.创建 SqlSessionFactory 工厂类
   方法:
   	openSession(); 获取SqlSession接口的实现类实例对象
 4.创建 SqlSession接口与实现类,主要封装CRUD方法
   方法:
   	selectList(String StatementId,Object param);查询所有
   	selectOne(String StatementId,Object param);查询单个
        close();释放资源
        具体实现  封装JDBC完成对数据库表的查询操作
        上述涉及到的设计模式: 构建者模式、工厂模式、代理模式
 5.Executor:Mybatis的执行器,是Mybatis调度的核心,负责sql语句的生成和查询缓存
 6.MappedStatement:MappedStatement是Mapper.xml中对select,update,delete,insert节点的封装
 7.Boundsql:表示动态生成的sql对应的参数信息

自定义框架实现

在使用端创建配置文件,提供框架端解析
创建sqlMapConfig.xml(准备工作)
  <configuration>
    <--数据库连接信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
    <--引入sql配置信息-->
    <mapper resource="mapper.xml"></mapper>
  </configuration>
创建mapper.xml(准备工作)
  <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实体类(准备工作)
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 + '\'' + '}';
  }
}
创建一个Maven子工程并且导入需要用到的依赖坐标(准备工作)
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
  <java.version>1.8</java.version>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
  <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>
</dependencies>
创建Configuration 类(准备工作)
public class Configuration {
        //数据源 存放sqlMapConfig.xml解析后内容,封装成JavaBean
        private DataSource dataSource;
        //map集合: key:statementId value:MappedStatement 
        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;
        }
}
5.创建MappedStatement 类(准备工作) sql对象 对应mapper.xml中内容
public class MappedStatement {
        //id
        private Integer id;
        //sql语句
        private String sql;
        //输入参数
        private Class<?> paramterType;
        //输出参数
        private Class<?> resultType;
        public Integer getId() {
        	return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getSql() {
            return sql;
        }
        public void setSql(String sql) {
            this.sql = sql;
        }
        public Class<?> getParamterType() {
            return paramterType;
        }
        public void setParamterType(Class<?> paramterType) {
            this.paramterType = paramterType;
        }
        public Class<?> getResultType() {
            return resultType;
        }
        public void setResultType(Class<?> resultType) {
            this.resultType = resultType;
        }
}
创建Resources 类(准备工作) 根据路径读取文件成输入流
public class Resources {
	/**
         * 当前类所在包下加载指定名称的文件,getClass是到当前列
         * InputStream in = this.getClass().getResourceAsStream("test.properties");
         * 从classpath根目录下加载指定名称的文件,这是因为 '/' 即代表根目录
         * InputStream in = this.getClass().getResourceAsStream("/test.properties");
         * 从classpath根目录下加载指定名称的文,这是因为getClassLoader就会到根目录上
         * InputStream in = this.getClass().getClassLoader().getResourceAsStream("test.properties");
        */
        //根据路径将文件读取数据以流的方式暂存(后续将流数据解析封装成JavaBean)
	public static InputStream getResourceAsSteam(String path){ 
  		InputStream resourceAsStream = Resources.class.getClassLoader.getResourceAsStream(path);
  		return resourceAsStream;
  	}
}
创建SqlSessionFactoryBuilder (SqlSessionFactory工厂)工厂构造器 调用build方法传入流根据解析流封装的Configuration创建SqlSessionFactory工厂
public class SqlSessionFactoryBuilder {
	//定义私有对象
	private Configuration configuration;
	public SqlSessionFactoryBuilder() {
		this.configuration = new Configuration();
	}
	public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {
		//1.解析配置文件,通过XMLConfigerBuilder解析流数据封装Configuration 
		xmlConfigerBuilder = new XMLConfigerBuilder(configuration); 
        	Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream);
		//2.创建 sqlSessionFactory 
        	SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
		return sqlSessionFactory;
	}
}
创建XMLConfigerBuilder 类 sqlMapConfig.xml配置解析器 读取流数据解析xml
public class XMLConfigerBuilder {
	private Configuration configuration;
	public XMLConfigerBuilder(Configuration configuration) {
		this.configuration = new Configuration();
	}
    	// 将传入的流解析封装成Configuration对象封装成Configuration对象
	public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {
		Document document = new SAXReader().read(inputStream); //取到<configuation>
		Element rootElement = document.getRootElement();
        	//取property节点下数据
		List<Element> propertyElements = rootElement.selectNodes("//property");
		Properties properties = new Properties();
		for (Element propertyElement : propertyElements) {
			String name = propertyElement.attributeValue("name");
			String value = propertyElement.attributeValue("value");
            		//暂存所有property节点下数据,方便后面获取
			properties.setProperty(name,value);
		}
                //使用连接池
                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
                configuration.setDataSource(comboPooledDataSource);
                //mapper 部分 取mapper节点下数据
                List<Element> mapperElements = rootElement.selectNodes("//mapper");
                //创建Mapper.xml文件解析器
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
                for (Element mapperElement : mapperElements) {
                	//取出文件路径
                	String mapperPath = mapperElement.attributeValue("resource");
                    //根据文件路径读取文件成流数据暂存
                    	InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
                  	xmlMapperBuilder.parse(resourceAsSteam);
                }
        	return configuration;
	}
}
XMLMapperBuilder mapper.xml数据信息解析器
public class XMLMapperBuilder {
	private Configuration configuration;
	public XMLMapperBuilder(Configuration configuration) {
		this.configuration = configuration;
	}
	public void parse(InputStream inputStream) throws DocumentException,ClassNotFoundException {
		Document document = new SAXReader().read(inputStream);
		Element rootElement = document.getRootElement();
		String namespace = rootElement.attributeValue("namespace");
		List<Element> select = rootElement.selectNodes("select");
		for (Element element : select) { //id的值
			String id = element.attributeValue("id");
			String paramterType = element.attributeValue("paramterType");
			String resultType = element.attributeValue("resultType"); 
            		//输入参数class
			Class<?> paramterTypeClass = getClassType(paramterType);
			//返回结果class
			Class<?> resultTypeClass = getClassType(resultType);
			//statementId
			String key = namespace + "." + id;
			//sql语句
			String textTrim = element.getTextTrim();
			//封装 mappedStatement
                        MappedStatement mappedStatement = new MappedStatement();
                        mappedStatement.setId(id);
                        mappedStatement.setParamterType(paramterTypeClass);
                        mappedStatement.setResultType(resultTypeClass);
                        mappedStatement.setSql(textTrim);
			//填充 configuration
			configuration.getMappedStatementMap().put(key, mappedStatement);
			private Class<?> getClassType (String paramterType) throws ClassNotFoundException {
				Class<?> aClass = Class.forName(paramterType);
				return aClass;
			}
		}
sqlSessionFactory 接口及DefaultSqlSessionFactory 实现类
public interface SqlSessionFactory {
	public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
	private Configuration configuration;
	public DefaultSqlSessionFactory(Configuration configuration) {
		this.configuration = configuration;
	}
	public SqlSession openSession(){
		return new DefaultSqlSession(configuration);
	}
}
sqlSession 接口及DefaultSqlSession 实现类
public interface SqlSession {
	public <E> List<E> selectList(String statementId, Object... param)Exception;
	public <T> T selectOne(String statementId,Object... params) throwsException;
	public void close() throws SQLException;
}
public class DefaultSqlSession implements SqlSession {
	private Configuration configuration;
	public DefaultSqlSession(Configuration configuration) {
	this.configuration = configuration;
	//处理器对象
	private Executor simpleExcutor = new SimpleExecutor();
	public <E > List < E > selectList(String statementId, Object...param) throws Exception {
		MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
		List<E> query = simpleExcutor.query(configuration, mappedStatement,param);
		return query;
	}
	//selectOne 中调用 selectList
	public <T > T selectOne(String statementId, Object...params) throwsException {
		List<Object> objects = selectList(statementId, params);
        	if (objects.size() == 1) {
        		return (T) objects.get(0);
        	} else {
			throw new RuntimeException("返回结果过多");
		}
	}
	public void close () throws SQLException {
		simpleExcutor.close();
	}
}
public interface Executor {
	<E> List<E> query(Configuration configuration, MappedStatementmappedStatement,Object[] param) throws Exception;void close() throws SQLException;
}
public class SimpleExecutor implements Executor {
	private Connection connection = null;
	public <E> List<E> query(Configuration configuration, MappedStatementmappedStatement, Object[] param) throws SQLException, NoSuchFieldException,IllegalAccessException, InstantiationException, IntrospectionException,InvocationTargetException {
		//获取连接
		connection = configuration.getDataSource().getConnection();
		// select * from user where id = #{id} and username = #{username} String
		sql = mappedStatement.getSql();
		//对sql进行处理
		BoundSql boundsql = getBoundSql(sql);
		// select * from where id = ? and username = ?
		String finalSql = boundsql.getSqlText();
		//获取传入参数类型
		Class<?> paramterType = mappedStatement.getParamterType();
        	//获取预编译preparedStatement对象
        	PreparedStatement preparedStatement =connection.prepareStatement(finalSql);
        	List<ParameterMapping> parameterMappingList =boundsql.getParameterMappingList();
        	for (int i = 0; i < parameterMappingList.size(); i++) {
        	ParameterMapping parameterMapping = parameterMappingList.get(i);
        	String name = parameterMapping.getName();
        	//反射
        	Field declaredField = paramterType.getDeclaredField(name);
        	declaredField.setAccessible(true);
        	//参数的值
        	Object o = declaredField.get(param[0]);
        	//给占位符赋值
        	preparedStatement.setObject(i + 1, o);
	}
	ResultSet resultSet = preparedStatement.executeQuery();
	Class<?> resultType = mappedStatement.getResultType();
	ArrayList<E> results = new ArrayList<E>();
	while (resultSet.next()) {
		ResultSetMetaData metaData = resultSet.getMetaData();
		(E) resultType.newInstance();
		int columnCount = metaData.getColumnCount();
		for (int i = 1; i <= columnCount; i++) {
			//属性名
			String columnName = metaData.getColumnName(i);
			//属性值
			Object value = resultSet.getObject(columnName);
			//创建属性描述器,为属性生成读写方法
			PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
			//获取写方法
			Method writeMethod = propertyDescriptor.getWriteMethod();
			//向类中写入值
			writeMethod.invoke(o, value);
		}
		results.add(o);
	}
		return results;
	}
	@Override
	public void close() throws SQLException {
		connection.close();
	}
	private BoundSql getBoundSql(String sql) {
		//标记处理类:主要是配合通用标记解析器GenericTokenParser类完成对配置文件等的解析工作,其中TokenHandler主要完成处理
		ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
		//GenericTokenParser :通用的标记解析器,完成了代码片段中的占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进行表达式的处理
		//三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处理器)
		GenericTokenParser genericTokenParser = new GenericTokenParser("# {","}", parameterMappingTokenHandler);
		String parse = genericTokenParser.parse(sql);
		List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
		BoundSql boundSql = new BoundSql(parse, parameterMappings);
		return boundSql;
	}
}

BoundSql
public class BoundSql {
	//解析过后的sql语句
	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;
	}	
}

自定义框架优化

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?
问题如下:
dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方
法,关闭 sqlsession)
dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码
解决:使用代理模式来创建接口的代理对象
@Test
public void test2() throws Exception {
	InputStream resourceAsSteam = Resources.getResourceAsSteam(path:"sqlMapConfig.xml")
	SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
	SqlSession sqlSession = build.openSession();
	User user = new User();
	user.setld(l);
	user.setUsername("tom");
	//代理对象
	UserMapper userMapper = sqlSession.getMappper(UserMapper.class);
	User userl = userMapper.selectOne(user);
	System・out.println(userl);
}
在sqlSession中添加方法
public interface SqlSession {
	public <T> T getMappper(Class<?> mapperClass);
    	实现类
	@Override
	public <T> T getMappper(Class<?> mapperClass) {
		T o = (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[] {mapperClass}, new InvocationHandler() {
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// selectOne
		String methodName = method.getName();
		// className:namespace
		String className = method.getDeclaringClass().getName();
		//statementid
		String key = className+"."+methodName; 
		Type genericReturnType = method.getGenericReturnType();
		ArrayList arrayList = new ArrayList<> ();
		//判断是否实现泛型类型参数化
		if(genericReturnType instanceof ParameterizedType){
			return selectList(key,args);
			return selectOne(key,args);
		}
	});
	return o;
}

自定义框架总结

Mybatis框架已经是一套拥有完善体系的产品了,占据了持久层框架使用的半壁江山,我们自定义的小框架只是对其原有设计思想的部分重现,实现了一个最简单的框架功能,只是为了我们能理解Mybatis框架的思想内核,方便对源码刨析时能更好的理解其思想