自定义持久层框架(参考拉钩Java课程)
分析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");
String sql = "select * from user where username = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "王五");
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) {
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 =
</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)
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
}
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 =
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
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
//属性名
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框架的思想内核,方便对源码刨析时能更好的理解其思想