一、分析JDBC操作问题
public static void main(String[] args) {
//数据库连接对象
Connection connection = null;
//预处理对象
PreparedStatement preparedStatement = null;
//结果集对象
ResultSet resultSet = null;
try {
//1.加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//2.通过驱动 获取数据库连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
//3.构造查询SQL
String sql = "select * from user where userName=?";
//4.通过连接获取预处理对象
preparedStatement = connection.prepareStatement(sql);
//5.设置参数
preparedStatement.setString(1, "lcg");
//6.查询数据库
resultSet = preparedStatement.executeQuery();
//7.遍历查询结果,封装成对象
while (resultSet.next()) {
String id = resultSet.getString("id");
String userName = resultSet.getString("userName");
System.out.println("id:" + id + ",userName=" + userName);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭resultSet
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭preparedStatement
if (preparedStatement != null) {
try {
preparedStatement.cancel();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭connection
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.1 问题分析
- 数据库配置信息存在硬编码
解决:将参数写到外部配置文件,然后通过读取配置文件来获取连接参数。 - 频繁创建、释放数据库连接,造成系统资源的浪费,从而影响系统性能。
解决:连接池 - sql语句preparedStatement传参存在硬编码
解决:将sql写到外部配置文件,程序加载配置文件,获取sql。 - 对结果集解析存在硬编码,sql变化会导致解析结果的代码发生变化
解决:利用JAVA的反射特性实现sql结果的解析。
1.2 问题解决思路总结
- 使用数据库连接池 统一管理 数据库连接;
- 将数据库连接信息、sql语句等抽取到xml配置文件中;
- 使用反射、内省等底层技术,自动将实体与表进行属性和字段的自动映射。
二、自定义框架设计
2.1 设计思路
使用端(项目)
需要提供核心配置文件,其中包括数据库配置信息以及sql配置信息;此外,还需引入我们后面写的自定义框架的jar包;另外,还需要测试使用的实体类以及dao
类。
配置文件:
-
sqlMapConfig.xml
:存放数据库连接信息,并且需要存放mapper.xml的所有路径。 -
mapper.xml
:存放sql配置信息。
框架端
- 读取配置文件(使用端会将配置文件以流的形式传递过来,然后转换成对象)
A.Configuration
类:存放数据库基本信息,并以Map
的形式(namespace.id
作为key
)存储sql语句;
B.MappedStatement
类:sql
的基本信息,包括其id、输入类型、输出类型、sql
语句。 - 解析配置文件
创建sqlSessionFactoryBuilder
类:
具体方法:public SqlSessionFactory build(InputStream in)
A. 使用dom4j
解析配置文件,将解析出来的内容封装到Configuration
中;
B. 创建sqlSessionFactory
对象:生产sqlSession
会话对象(工厂模式) - 创建
SqlSessionFactory
接口及实现类:
⽅法:openSession()
: 获取sqlSession
接⼝的实现类实例对象 - 创建
sqlSession
接⼝及实现类DefaultSqlSession
:主要封装crud
⽅法
⽅法:
selectList(String statementId,Object param)
:查询所有
selectOne(String statementId,Object param)
:查询单个 - 创建
Executor
接口及其实现类SimpleExecutor
实现类
将sqlSession
的JDBC
代码封装在这里。 - 使用
JDK
动态代理来为Dao
接口生成代理对象,通过代理对象调用方法都会执行invoke
方法,从而去调用DefaultSqlSession
的具体方法。(代理模式)
2.2 自定义持久层框架实现
2.2.1 使用端(项目)
sqlMapConfig.xml
<configuration>
<!-- 数据库连接信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/simple_db"></property>
<property name="username" value="root"></property>
<property name="password" value="admin123"></property>
<!-- 引入所有的sql配置文件 -->
<mappers>
<mapper resource="sysUserMapper.xml"></mapper>
</mappers>
</configuration>
sysUserMapper.xml
<mapper namespace="com.alex.dao.SysUserDao">
<select id="selectAll" resultType="com.alex.entity.SysUser">
select *
from sys_user
</select>
<select id="selectOne" parameterType="com.alex.entity.SysUser" resultType="com.alex.entity.SysUser">
select *
from sys_user
where id = #{id} and name = #{name}
</select>
</mapper>
SysUser.java
package com.alex.entity;
public class SysUser {
private String id;
private String name;
private Integer age;
public SysUser() {
}
public SysUser(String id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "SysUser{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
SysUserDao.java
package com.alex.dao;
import com.alex.entity.SysUser;
import java.util.List;
public interface SysUserDao {
List<SysUser> selectAll();
SysUser selectOne(SysUser sysUser);
}
2.2.2 框架端
Resources.java
package com.alex.io;
import java.io.InputStream;
public class Resources {
public static InputStream getResourceAsStream(String path) {
//从classpath的根路径获取配置文件
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
Configuration.java
package com.alex.entity;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
/**
* 数据源
*/
private DataSource dataSource;
/**
* 所有的sql信息
*/
private 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.java
package com.alex.entity;
public class MappedStatement {
private String id;
private String parameterType;
private String resultType;
private String sqlText;
public MappedStatement() {
}
public MappedStatement(String id, String parameterType, String resultType, String sqlText) {
this.id = id;
this.parameterType = parameterType;
this.resultType = resultType;
this.sqlText = sqlText;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSqlText() {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
}
SqlSessionFactoryBuilder.java
package com.alex.sqlSession;
import com.alex.config.SqlMapConfigBuilder;
import com.alex.entity.Configuration;
import org.dom4j.DocumentException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
// 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
SqlMapConfigBuilder sqlMapConfigBuilder = new SqlMapConfigBuilder();
Configuration configuration = sqlMapConfigBuilder.config(in);
// 第二:创建sqlSessionFactory对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
DefaultSqlSession.java
package com.alex.sqlSession;
import com.alex.entity.Configuration;
import com.alex.entity.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
SimpleExecutor 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<Object> list = this.selectList(statementId, params);
if (list.size() == 0) {
return null;
}
if (list.size() == 1) {
return (T) list.get(0);
}
throw new RuntimeException("返回结果过多.");
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象;通过代理对象调用方法都会执行invoke方法
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.准备参数 statementId
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
Type genericReturnType = method.getGenericReturnType();
// 2.调用方法
// ParameterizedType 表示参数化类型,例如 Collection<String>。
if (genericReturnType instanceof ParameterizedType) {
return selectList(statementId, args);
}
return selectOne(statementId, args);
}
});
return (T) proxyInstance;
}
}
SimpleExecutor.java
package com.alex.sqlSession;
import com.alex.config.BoundSql;
import com.alex.entity.Configuration;
import com.alex.entity.MappedStatement;
import com.alex.utils.GenericTokenParser;
import com.alex.utils.ParameterMapping;
import com.alex.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 {
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1.获取连接
Connection connection = configuration.getDataSource().getConnection();
// 2.转换sql语句 #{}转占位符?,并获取参数列表
String sql = mappedStatement.getSqlText();
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理statement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4.设置参数
String parameterTypeName = mappedStatement.getParameterType();
Class<?> parameterTypeClass = getClassType(parameterTypeName);
for (int i = 0; i < boundSql.getParameterMappingList().size(); i++) {
ParameterMapping parameterMapping = boundSql.getParameterMappingList().get(i);
// 利用反射特性,寻找value
Field field = parameterTypeClass.getDeclaredField(parameterMapping.getContent());
// 暴力访问
field.setAccessible(true);
Object o = field.get(params[0]);
preparedStatement.setObject(i + 1, o);
}
// 5.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 6.封装结果集
List<Object> list = new ArrayList<>();
Class<?> resultTypeClass = getClassType(mappedStatement.getResultType());
while (resultSet.next()) {
Object item = resultTypeClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 获取参数名称
String columnName = metaData.getColumnName(i);
// 获取参数value,这里只用varchar和int举例
int columnType = metaData.getColumnType(i);
Object value = null;
if (columnType == Types.INTEGER) {
value = resultSet.getInt(columnName);
} else if (columnType == Types.VARCHAR) {
value = resultSet.getString(columnName);
}
// 利用反射以及内省(属性描述器)完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method write = propertyDescriptor.getWriteMethod();
write.invoke(item, value);
}
list.add(item);
}
return (List<E>) list;
}
private Class<?> getClassType(String parameterTypeName) throws ClassNotFoundException {
if (parameterTypeName != null && parameterTypeName.length() > 0) {
return Class.forName(parameterTypeName);
}
return null;
}
private BoundSql getBoundSql(String sql) {
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
// sql
String parseSql = parser.parse(sql);
// 参数
List<ParameterMapping> parameterMappingList = tokenHandler.getParameterMappings();
return new BoundSql(parseSql, parameterMappingList);
}
}
其他代码略,可以到这里阅读。
2.2.3 测试
package test;
import com.alex.dao.SysUserDao;
import com.alex.entity.SysUser;
import com.alex.io.Resources;
import com.alex.sqlSession.SqlSession;
import com.alex.sqlSession.SqlSessionFactory;
import com.alex.sqlSession.SqlSessionFactoryBuilder;
import org.dom4j.DocumentException;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
public class IPersistentTest {
@Test
public void test() throws PropertyVetoException, DocumentException {
InputStream resourceAsSteam = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
// test
SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
System.out.println("Test: selectOne");
SysUser user = new SysUser("1", "xx", null);
SysUser find = sysUserDao.selectOne(user);
System.out.println(find);
System.out.println("");
System.out.println("Test: selectAll");
List<SysUser> list = sysUserDao.selectAll();
for (SysUser item : list) {
System.out.println(item);
}
}
}
运行结果
网友评论