一、分析JDBC操作問題
public static void main(String[] args) {
//數(shù)據(jù)庫連接對象
Connection connection = null;
//預(yù)處理對象
PreparedStatement preparedStatement = null;
//結(jié)果集對象
ResultSet resultSet = null;
try {
//1.加載數(shù)據(jù)庫驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
//2.通過驅(qū)動 獲取數(shù)據(jù)庫連接對象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
//3.構(gòu)造查詢SQL
String sql = "select * from user where userName=?";
//4.通過連接獲取預(yù)處理對象
preparedStatement = connection.prepareStatement(sql);
//5.設(shè)置參數(shù)
preparedStatement.setString(1, "lcg");
//6.查詢數(shù)據(jù)庫
resultSet = preparedStatement.executeQuery();
//7.遍歷查詢結(jié)果,封裝成對象
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 {
//關(guān)閉resultSet
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//關(guān)閉preparedStatement
if (preparedStatement != null) {
try {
preparedStatement.cancel();
} catch (SQLException e) {
e.printStackTrace();
}
}
//關(guān)閉connection
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.1 問題分析
- 數(shù)據(jù)庫配置信息存在硬編碼
解決:將參數(shù)寫到外部配置文件,然后通過讀取配置文件來獲取連接參數(shù)。 - 頻繁創(chuàng)建、釋放數(shù)據(jù)庫連接,造成系統(tǒng)資源的浪費,從而影響系統(tǒng)性能。
解決:連接池 - sql語句preparedStatement傳參存在硬編碼
解決:將sql寫到外部配置文件,程序加載配置文件,獲取sql。 - 對結(jié)果集解析存在硬編碼,sql變化會導(dǎo)致解析結(jié)果的代碼發(fā)生變化
解決:利用JAVA的反射特性實現(xiàn)sql結(jié)果的解析。
1.2 問題解決思路總結(jié)
- 使用數(shù)據(jù)庫連接池 統(tǒng)一管理 數(shù)據(jù)庫連接;
- 將數(shù)據(jù)庫連接信息、sql語句等抽取到xml配置文件中;
- 使用反射、內(nèi)省等底層技術(shù),自動將實體與表進行屬性和字段的自動映射。
二、自定義框架設(shè)計
2.1 設(shè)計思路
使用端(項目)
需要提供核心配置文件,其中包括數(shù)據(jù)庫配置信息以及sql配置信息;此外,還需引入我們后面寫的自定義框架的jar包;另外,還需要測試使用的實體類以及dao類。
配置文件:
-
sqlMapConfig.xml:存放數(shù)據(jù)庫連接信息,并且需要存放mapper.xml的所有路徑。 -
mapper.xml:存放sql配置信息。
框架端
- 讀取配置文件(使用端會將配置文件以流的形式傳遞過來,然后轉(zhuǎn)換成對象)
A.Configuration類:存放數(shù)據(jù)庫基本信息,并以Map的形式(namespace.id作為key)存儲sql語句;
B.MappedStatement類:sql的基本信息,包括其id、輸入類型、輸出類型、sql語句。 - 解析配置文件
創(chuàng)建sqlSessionFactoryBuilder類:
具體方法:public SqlSessionFactory build(InputStream in)
A. 使用dom4j解析配置文件,將解析出來的內(nèi)容封裝到Configuration中;
B. 創(chuàng)建sqlSessionFactory對象:生產(chǎn)sqlSession會話對象(工廠模式) - 創(chuàng)建
SqlSessionFactory接口及實現(xiàn)類:
?法:openSession(): 獲取sqlSession接?的實現(xiàn)類實例對象 - 創(chuàng)建
sqlSession接?及實現(xiàn)類DefaultSqlSession:主要封裝crud?法
?法:
selectList(String statementId,Object param):查詢所有
selectOne(String statementId,Object param):查詢單個 - 創(chuàng)建
Executor接口及其實現(xiàn)類SimpleExecutor實現(xiàn)類
將sqlSession的JDBC代碼封裝在這里。 - 使用
JDK動態(tài)代理來為Dao接口生成代理對象,通過代理對象調(diào)用方法都會執(zhí)行invoke方法,從而去調(diào)用DefaultSqlSession的具體方法。(代理模式)
2.2 自定義持久層框架實現(xiàn)
2.2.1 使用端(項目)
sqlMapConfig.xml
<configuration>
<!-- 數(shù)據(jù)庫連接信息 -->
<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 {
/**
* 數(shù)據(jù)源
*/
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解析配置文件,將解析出來的內(nèi)容封裝到Configuration中
SqlMapConfigBuilder sqlMapConfigBuilder = new SqlMapConfigBuilder();
Configuration configuration = sqlMapConfigBuilder.config(in);
// 第二:創(chuàng)建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("返回結(jié)果過多.");
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK動態(tài)代理來為Dao接口生成代理對象;通過代理對象調(diào)用方法都會執(zhí)行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.準備參數(shù) statementId
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
Type genericReturnType = method.getGenericReturnType();
// 2.調(diào)用方法
// ParameterizedType 表示參數(shù)化類型,例如 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.轉(zhuǎn)換sql語句 #{}轉(zhuǎn)占位符?,并獲取參數(shù)列表
String sql = mappedStatement.getSqlText();
BoundSql boundSql = getBoundSql(sql);
// 3.獲取預(yù)處理statement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4.設(shè)置參數(shù)
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.執(zhí)行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 6.封裝結(jié)果集
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++) {
// 獲取參數(shù)名稱
String columnName = metaData.getColumnName(i);
// 獲取參數(shù)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);
}
// 利用反射以及內(nèi)?。▽傩悦枋銎鳎┩瓿煞庋b
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);
// 參數(shù)
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);
}
}
}
運行結(jié)果
