框架优化
上节已经实现了部分内容,下面继续。
上述我们编写了自定义的框架,解决了 JDBC 带来的一系列问题,但是目前也出现了一些问题:
- DAO 的实现类存在重复的代码,整个操作模板重复,创建 SqlSession 等等
- DAO 的实现类中有硬编码,调用 SqlSession 的方法时,参数 Statement 的 ID 硬编码
SqlSession
解决:使用代理模式来创建接口的代理对象,我们在 SqlSession 中加入新的方法:getMapper
<T> T getMapper(Class<?> mapperClass);
DefaultSqlSession
我们在实现类中进行实现刚才的 getMapper 的方法:
@Override
public <T> T getMapper(Class<?> mapperClass) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
代码解析
方法说明:
@Override:表示该方法是对父类或接口方法的重写T:定义了一个泛型方法,T 是返回值的类型getMapper(Class<?> mapperClass):方法接收一个 Class 类型参数,表示需要生成的 Mapper 接口的类型
Proxy.newProxyInstance 参数:
DefaultSqlSession.class.getClassLoader():指定类加载器new Class[]{mapperClass}:指定代理类需要实现的接口数组new InvocationHandler():传入 InvocationHandler 处理方法调用逻辑
invoke 方法参数:
Object proxy:代理对象本身Method method:当前被调用的方法对象Object[] args:调用方法时传递的参数
方法调用逻辑
String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
获取方法名,检查方法是否是 Object 类的方法(如 toString()、equals() 等)。如果是,直接调用原始实现。
SQL 语句标识符
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
拼接方法所在类名和方法名,生成唯一的 SQL 语句标识符。
方法返回类型判断
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
List<Object> objects = selectList(statementId, args);
return objects;
}
如果返回类型是参数化类型(例如 List),则调用 selectList 方法执行查询;否则调用 selectOne。
测试方法
package icu.wzk.test;
import icu.wzk.bean.Resources;
import icu.wzk.bean.SqlSession;
import icu.wzk.bean.SqlSessionFactory;
import icu.wzk.bean.SqlSessionFactoryBuilder;
import icu.wzk.dao.UserInfoMapper;
import icu.wzk.model.UserInfo;
import java.io.InputStream;
public class Test02 {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wzk");
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
System.out.println("userInfoMapper: " + userInfoMapper);
System.out.println(userInfoMapper.selectOne(userInfo));
}
}
运行结果
log4j:WARN No appenders could be found for logger (com.mchange.v2.log.MLog).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
userInfoMapper: icu.wzk.bean.DefaultSqlSession$1@61dc03ce
SimpleExecutor getBoundSql: SELECT * FROM user_info WHERE username=?
UserInfo(id=1, username=wzk, password=icu, age=18)