一.自定义框架设计
(一)使用设计
1. 创建数据库及user表
2. 创建pojo.User类,代码示例请点击
3. 创建核心配置文件
resources.sqlMapConfig.xml: 配置数据源信息,链接UserMapper.xml。 代码示例请点击
resources.UserMapper.xml: 配置要执行的SQL语句。代码示例请点击
4.创建dao层IUserDao接口,代码示例请点击
(二)框架设计
1.加载读取使用端配置文件-准备阶段
使用Resources类InputSteam getResourceAsSteam(String path)将配置文件加载成字节输入流,并创建两个JavaBean用来存储配置文件输入流信息。
(1)创建io.Resources类,代码示例请点击
(2)创建pojo.Configuration类 使用此JavaBean用来存储数据源相关信息及封装MappedStatement的SQL语句集合。代码示例请点击
(3)创建pojo.MappedStatement类,使用此JavaBean用来封装配置文件中单条SQL语句及输入、输出参数类型。代码示例请点击
2.解析配置文件-构建阶段
创建sqlSessionFactoryBuilder构建类,在此类中创建sqlSessionFactory build(InputSteam in)方法,调用xmlConfigBuilder类的parseConfig(in)方法将dom4j解析出来的数据源配置文件信息封装到pojo.Configuration中,再在xmlConfigBuilder类中调用XMLMapperBuilder类,用来将SQL执行语句及输入输出参数类型封装到pojo.MappedStatement中,最后将.MappedStatement集合封装到pojo.Configuration中。
(1)创建sqlSession.sqlSessionFactoryBuilder类,代码示例请点击
(2)创建config.xmlConfigBuilder类,代码示例请点击
(3)创建config.XMLMapperBuilder类,代码示例请点击
3. 创建SqlSessionFactory工厂类,并生产sqlSession-生产阶段,代码示例点击
(1)创建SqlSessionFactory接口及实现类DefaultSqlSessionFacotry,使用openSession() 方法: 获取sqlSession接⼝的实现类DefaultSqlSession实例对象,也就是生产sqlSession。
(2)在DefaultSqlSession类中使用JDK动态代理模式,访问sqlSession.getMapper方法时,生成代理对象,实际访问:
new InvocationHandler()内部类中的invoke方法
在invoke方法中,通过判断选择要去执行哪条SQL语句对应的方法。这里使用动态代理机制,可以不用再写IUserDao实现类,如下图:
4.创建Executor接口及实现类SimpleExecutor实现类。
通过query()方法,执行底层JDBC代码
(1)常规的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, "tom");
// 向数据库发出sql执⾏查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
} 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) {
e.printStackTrace();
}
}
}
JDBC问题总结:
原始jdbc开发存在的问题如下:
1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤, sql变动需要改变java代码。
3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能 少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名), sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成 pojo对象解析⽐较⽅便
(2)在生产出来的一个个的DefaultSqlSession中,我们将调用
simpleExecutor.query(configuration, mappedStatement, params)方法来执行底层的JDBC代码。
Executor接口代码如下:
package com.lagou.sqlSession;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import java.util.List;
public interface Executor {
public <E> List<E> query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;
}
SimpleExecutor实现类代码如下:
package com.lagou.sqlSession;
import com.lagou.config.BoundSql;
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.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
public class simpleExecutor implements Executor {
@Override //user
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
// 2. 获取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);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. 设置参数
//获取到了参数的全路径
String paramterType = mappedStatement.getParamterType();
//调用getClassType方法
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);
//防止私有变量,开放该成员变量的访问权限,暴力访问
declaredField.setAccessible(true);
//通过参数对象获取指定的成员变量的值,o代表成员变量的值,declaredField代表成员变量,params[0]表示该成员变量所属的实例化对象
Object o = declaredField.get(params[0]);
//开始设置参数,i从0开始,因此用i+1
preparedStatement.setObject(i+1,o);
}
// 5. 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
ArrayList<Object> objects = new ArrayList<>();
// 6. 封装返回结果集
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;
}
private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
if(paramterType!=null){
Class<?> aClass = Class.forName(paramterType);
return aClass;
}
return null;
}
/**
* 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
//标记处理类:配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//#{}里面解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
return boundSql;
}
}
(3)以上代码将配置文件中解析出来的SQL语句及参数格式如下:
<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
这个格式是无法被JDBC预处理对象PreparedStatement所识别,因此要通过转换格式,这里用到了mybatis框架的部分源码。我们通过SimpleExecutor实现类的query()方法中调用getBoundSql(sql)方法,在方法中先构建ParameterMappingTokenHandler对象将例如:#{id}格式转化成能被识别的参数格式为 :?符号;其次构建genericTokenParser对象,用来转换及存储转化后的SQL执行语句,然后将能被识别的SQL语句及参数封装到BoundSql对象中放回。
(三)项目结构及pom.xml
1. 使用端结构:
2.使用端pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lagou</groupId>
<artifactId>IPersistence_test</artifactId>
<version>1.0-SNAPSHOT</version>
<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>com.lagou</groupId>
<artifactId>IPersistence</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3. 自定义框架端结构:
4. 自定义框架端pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lagou</groupId>
<artifactId>IPersistence</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
</project>
二. 自定义mybatis持久层框架所涉及的主要技术知识点
(一)工厂设计模式,请点击:
《⼯⼚设计模式介绍及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客》
《设计模式之抽象工厂模式:如何统一不同代码风格下的代码级别?》、《设计模式之工厂方法模式:如何解决生成对象时的不确定性?》
(二)建造者设计模式,请点击:
《Builder构建者模式及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客》
(三)ResultSetMetaData类介绍,请点击:
(四)Properties类介绍,请点击:
(五)JAVA反射机制,请点击:
(六)JDK动态代理机制,请点击:
《动态代理模式的介绍及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客》