Framework Optimization

The previous section implemented part of the content. Let’s continue below.

Above we wrote a custom framework that solved a series of problems brought by JDBC, but some problems have also appeared:

  • DAO implementation class has repeated code, entire operation template repeats, creating SqlSession, etc.
  • DAO implementation class has hard-coded values, Statement ID parameters are hard-coded when calling SqlSession methods

SqlSession

Solution: Use proxy pattern to create interface proxy objects. We add a new method to SqlSession: getMapper

<T> T getMapper(Class<?> mapperClass);

DefaultSqlSession

We implement the getMapper method in the implementation class:

@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;
}

Code Analysis

Method Description:

  • @Override: Indicates the method overrides parent class or interface method
  • T: Defines a generic method, T is the return value type
  • getMapper(Class<?> mapperClass): Method accepts a Class type parameter representing the type of Mapper interface to generate

Proxy.newProxyInstance Parameters:

  • DefaultSqlSession.class.getClassLoader(): Specifies class loader
  • new Class[]{mapperClass}: Specifies interface array that proxy class needs to implement
  • new InvocationHandler(): Passes InvocationHandler to handle method call logic

invoke Method Parameters:

  • Object proxy: The proxy object itself
  • Method method: The currently called method object
  • Object[] args: Parameters passed when calling method

Method Call Logic

String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
}

Get method name, check if method is a method in Object class (such as toString(), equals(), etc.). If so, directly call original implementation.

SQL Statement Identifier

String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;

Concatenate method’s class name and method name to generate unique SQL statement identifier.

Method Return Type Judgment

Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
    List<Object> objects = selectList(statementId, args);
    return objects;
}

If return type is parameterized type (such as List), call selectList method to execute query; otherwise call selectOne.

Test Method

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));
    }
}

Running Results

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)