一、分析JDBC操作问题
public class Test {
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();
}
}
}
}
}
-
数据库配置信息存在硬编码问题
解决方案:利用配置文件 -
频繁创建释放数据库链接
解决方案:连接池 -
sql语句,设置参数,获取结果集参数 均存在硬编码问题
解决方案:利用配置文件,但是由于这几项信息是频繁改动的内容,所以不应该和数据库配置信息放在同一份配置文件中,应该分开存放 -
手动封装返回结果集,较为繁琐
解决方案:反射、内省
二、自定义框架设计思路分析
-
使用端(项目)
- 引入自定义持久层框架的jar包
- 提供两部分配置信息
- sqlMapConfig.xml : 存放数据源信息
- Mapper.xml : sql 语句的配置文件信息
-
自定义持久层框架本身(工程)
- 加载配置文件
根据配置文件的路劲,加载配置文件成字节输入流,存入内存中
创建 Resources 类,方法:InputSteam getResourceAsSteam(String path)
- 创建两个javaBean(容器对象)
存放对配置文件解析出来的内容
Configuration(核心配置类) : 存放 sqlMapConfig.xml 解析出来的内容
MappedStatement(映射配置类):存放 Mapper.xml 解析出来的内容 - 使用dom4j解析配置文件
创建 sqlSessionFactoryBuilder 类,方法:build(InputSteam in)
创建 SqlSessionFactory 对象,生产 SqlSession - 创建 SqlSessionFactory 接口及实现类 DefaultSqlSessionFactory
方法:openSession
生产SqlSession - 创建SqlSession接口及实现类DefaultSession
定义对数据库的CRUD操作 - 创建Executor接口及实现类SimpleExecutor
方法:query(Configuration c, MappedStatement m, Object...params)
执行JDBC代码
- 加载配置文件
三、使用端代码编写
sqlMapConfig.xml 定义数据源信息
<configuration>
<!--数据库配置信息-->
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///wujun_test"></property>
<property name="username" value="wujun"></property>
<property name="password" value="wujun@2020"></property>
</dataSource>
<!--存放mapper.xml的全路径-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
UserMapper.xml 定义 sql 信息
SQL唯一标识(StatementId)由 namespace.id 来组成
例如:com.wujun.dao.IUserDao.findAll
<mapper namespace="com.wujun.dao.IUserDao">
<select id="findAll" resultType="com.wujun.pojo.User">
select * from user
</select>
<select id="findByCondition" resultType="com.wujun.pojo.User" paramterType="com.wujun.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
四、自定义持久层框架代码编写
- 创建 Resources 类,加载配置文件
package com.wujun.io;
import java.io.InputStream;
public class Resources {
// 借助类加载器,将指定路劲的配置文件加载成字节输入流,存储在内存中
public static InputStream getResourceAsSteam(String path) {
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
- 创建两个 javaBean(容器对象)用来存放配置文件解析出来的信息
/**
* 存放解析出来的数据源对象 以及 封装 MappedStatement
* 以便于后续作为参数层层传递,最后进行JDBC操作
*/
@Data
public class Configuration {
private DataSource dataSource;
/**
* key:StatementId(由 namespace.id 组成)
*/
private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
/**
* 针对SQL语句配置文件解析之后,用来存放的对象
* 通俗的来说,有几个select标签,就会生成几个MappedStatement对象
*/
@Data
public class MappedStatement {
/**
* id标识
*/
private String id;
/**
* 返回值类型
*/
private String resultType;
/**
* 参数值类型
*/
private String parameterType;
/**
* SQL语句
*/
private String sql;
}
- 使用 dom4j 解析配置文件
/**
* XML解析
*/
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
/**
* 使用dom4j对配置文件进行解析,封装成 Configuration 对象
*/
public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(in);
// 拿到配置文件的根对象 <configuration>
Element rootElement = document.getRootElement();
// 查找所有的 property 标签
List<Element> elementList = rootElement.selectNodes("//property");
// 遍历 elementList
Properties properties = new Properties();
for (Element element : elementList) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name, value);
}
// 使用c3p0数据库连接池存放数据源信息
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.setDataSource(comboPooledDataSource);
// 解析sql配置文件信息,存入 configuration 中
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream mapperResource = Resources.getResourceAsSteam(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(mapperResource);
}
return configuration;
}
}
/**
* Mapper XML解析
*/
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
/**
* 使用dom4j对 mapper.xml 配置文件进行解析,放入 Configuration 对象
*/
public void parse(InputStream in) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(in);
// 拿到配置文件的根对象 <mapper>
Element rootElement = document.getRootElement();
// 获取 namespace
String namespace = rootElement.attributeValue("namespace");
// 查找所有的 select 标签
List<Element> elementList = rootElement.selectNodes("//select");
// 遍历 elementList
for (Element element : elementList) {
String selectId = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(selectId);
mappedStatement.setParameterType(parameterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
configuration.getMappedStatementMap().put(namespace + "." + selectId, mappedStatement);
}
}
}
- 创建SqlSession
public class SqlSessionFactoryBuild {
/**
* 解析配置文件,创建 SqlSession
*/
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
// 1、使用dom4j解析配置文件,将解析出来的内容封装到 Configuration 中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(in);
// 2、创建SqlFactory对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
- 定义对数据库的CRUD操作
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 simpleExecutor = new SimpleExecutor(configuration);
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> list = selectList(statementId, params);
if (list != null && list.size() == 1) {
return (T) list.get(0);
} else {
throw new RuntimeException("查询得到的结果为空或者查询得到多个结果");
}
}
}
- 在SimpleExecutor 中编写JDBC代码
public class SimpleExecutor implements Executor {
private Configuration configuration;
public SimpleExecutor(Configuration configuration) {
this.configuration = configuration;
}
@Override
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.getSql());
// 4、设置参数
String parameterType = mappedStatement.getParameterType();
Class<?> parameterTypeClass = getClassType(parameterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
Field declaredField = parameterTypeClass.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
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.getDeclaredConstructor().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;
}
/**
* 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
* 具体工具类都是从 Mybatis 中直接拿来使用的
*/
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;
}
private Class<?> getClassType(String type) throws ClassNotFoundException {
if (type != null) {
Class<?> aClass = Class.forName(type);
return aClass;
}
return null;
}
}
- 使用端编写测试类
package com.wujun.test;
import com.alibaba.fastjson.JSON;
import com.wujun.io.Resources;
import com.wujun.pojo.User;
import com.wujun.sqlSession.SqlSession;
import com.wujun.sqlSession.SqlSessionFactory;
import com.wujun.sqlSession.SqlSessionFactoryBuild;
import org.junit.Test;
import java.io.InputStream;
public class IPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuild().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(1L);
user.setUsername("吴俊1");
User sqlResult = sqlSession.selectOne("com.wujun.dao.IUserDao.findByCondition", user);
System.out.println(JSON.toJSONString(sqlResult));
}
}
- 测试返回结果
Connected to the target VM, address: '127.0.0.1:60755', transport: 'socket'
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.dom4j.io.SAXContentHandler (file:/D:/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar) to method com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser$LocatorProxy.getEncoding()
WARNING: Please consider reporting this to the maintainers of org.dom4j.io.SAXContentHandler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
log4j:WARN No appenders could be found for logger (com.mchange.v2.log.MLog).
log4j:WARN Please initialize the log4j system properly.
{"id":1,"username":"吴俊1"}
Disconnected from the target VM, address: '127.0.0.1:60755', transport: 'socket'
Process finished with exit code 0
-
数据库截图