The use of javassist, and the simple implementation of the GenerateDaoProxy mechanism (getMapper method) in MyBatis using javassist

The use of javassist, and the simple implementation of the GenerateDaoProxy mechanism in MyBatis using javassist

    • 1. What is javassist?
    • 2. Simply use javassist: here we only consider simple implementation classes through interfaces
    • 3. Provide tool classes for SqlSession
    • 4. Dynamically generate a simple Dao implementation class:

1. What is javassist?

Javaassist is a class library that can process Java bytecode, which means you can use Javassist to dynamically generate implementation classes of interfaces.
Classes can be generated dynamically in memory.

2. Simply use javassist: here we only consider simple implementation classes through interfaces

Provide an interface:

public interface AccountDao {<!-- -->
    void delete();
}
Create a class through javassist and implement this interface:
The first step: Get the class pool. This class pool is used to generate classes.
Step 2: Manufacturing
Step 3: Generate interface
Step 4: Add the interface to the class
Step 5: How to make it
Step 6: Add the method to the class
Step 7: Instantiate the class and call the method
 public void testGenerateImpl() throws Exception{<!-- -->
        // Get the class pool
        ClassPool pool = ClassPool.getDefault();
        // Generate class
        CtClass ctClass = pool.makeClass("AccountDaoImpl");
        // Generate interface
        CtClass ctInterface = pool.makeInterface("AccountDao");
        //Add interface to class
        ctClass.addInterface(ctInterface);
        // Implement methods in the interface
        // Manufacturing method, the first parameter is the string of the entire method, and the second parameter is the loaded class object
        CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(1233);}", ctClass);
        //Add the method to the class
        ctClass.addMethod(ctMethod);
        // Generate classes and load the generated classes into the JVM
        Class<?> clazz = ctClass.toClass();
        AccountDao account = (AccountDao) clazz.newInstance();
        account.delete();

    }

The configuration needs to be added to the running configuration:
–add-opens java.base/java.lang=ALL-UNNAMED
–add-opens java.base/sun.net.util=ALL-UNNAMED
Configuration information

3. Provide tool classes for SqlSession

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

public class SqlSessionUtil {<!-- -->
    private SqlSessionUtil(){<!-- -->}

    private static SqlSessionFactory sqlSessionFactory;
    static {<!-- -->
        try {<!-- -->
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {<!-- -->
            throw new RuntimeException(e);
        }
    }
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    public static SqlSession openSession(){<!-- -->
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {<!-- -->
            sqlSession = sqlSessionFactory.openSession();
            // Bind the sqlSession object to the current thread
            local.set(sqlSession);
        }
        return sqlSession;
    }
    //Close the sqlSession object
    public static void close(SqlSession sqlSession){<!-- -->
        if (sqlSession != null) {<!-- -->
            sqlSession.close();
            //Remove the binding relationship between the sqlSession object and the current thread from the current thread
            local.remove();
        }
    }
}

The purpose of using ThreadLocal: In order to facilitate transaction control in the business, it is necessary to ensure that the SqlSession obtained by the same thread is the same SqlSession.
Note: The file name and path of the mybatis configuration file here are hard-coded.
The file name is: mybatis-config.xml, and the path is the root path where class resources are loaded.

4. Dynamically generate a simple Dao implementation class:

java pojo class:

public class Account {<!-- -->
    private Long id;
    private String actno;
    private Double balance;
    // Omit construction methods, get, set methods, toString methods, etc.
}

Dao interface:

public interface AccountDao {<!-- -->
    /**
     * Query account information based on account number
     * @param actno account
     * @return account information
     */
    Account selectByActno(String actno);

    /**
     * Update account information based on account number
     * @param account account number
     * @return The number of updated records
     */
    int updateByActno(Account account);
}

javassist implements the interface:

import com.bjpowernode.bank.dao.AccountDao;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;

/*
    Dynamically generate Dao implementation class
 */
public class GenerateDaoProxy {<!-- -->

    /**
     * Generate the implementation class of dao, create and return the object of this class
     * @param daoInterface If the dao interface is implemented
     * @return
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface){<!-- -->
        //class pool
        ClassPool pool = ClassPool.getDefault();
        //manufacturing class
        CtClass ctClass = pool.makeClass(daoInterface + "Proxy");
        //Manufacturing interface
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        //Methods to implement the interface
        ctClass.addInterface(ctInterface);
        //Add method to class
        Method[] methods = AccountDao.class.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {<!-- -->
        // Use StringBuilder to splice each method body
            // method is an abstract method in the interface
            // Implement the method abstract method
            CtMethod ctMethod = null;
            try {<!-- -->
            // This part is the modifier list + return value type + method name + formal parameter list
                StringBuilder methodStr = new StringBuilder();
                methodStr.append("public ");
                // Get the return value type
                String returnType = method.getReturnType().getName();
                methodStr.append(returnType + " ");
                // Get method name
                String name = method.getName();
                methodStr.append(name);
                methodStr.append("(");
                // Get all parameters
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i + + ) {<!-- -->
                    String parameterTypeName = parameterTypes[i].getName();
                    methodStr.append(parameterTypeName);
                    methodStr.append(" ");
                    methodStr.append("arg" + i);
                    if(i != parameterTypes.length-1){<!-- -->
                        methodStr.append(",");
                    }
                }
                methodStr.append("){");
                // This part is the code part
                // This part is dead, the first line is this
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = SqlSessionUtil.openSession();");
                // Need to know what type of sql statement it is
                /**
                 * sqlSession.getConfiguration() gets the configuration in xxxMapper
                 * sqlSession.getConfiguration().getMappedStatement("id") You can get a sql statement in this mapper through this id
                 * sqlSession.getConfiguration().getMappedStatement("id").getStatementType() can get which sql statement it is
                 *SqlCommandType is an enumeration
                 * UNKNOWN,
                 *INSERT,
                 *UPDATE,
                 *DELETE,
                 *SELECT,
                 *FLUSH;
                 * However, the ID of the SQL statement is provided by the user of the framework and is changeable. For developers of my framework. I have no idea
                 * Since the developer of my framework does not know the sqlId, what should I do? The developers of the mybatis framework have issued a rule: Anyone who uses the GenerateDaoProxy mechanism.
                 * sqlId cannot be written casually. namesapce must be the fully qualified name of the dao interface. id must be the method name of the interface.
                 * Therefore sqlId is "interface name. method name"
                 */
                String sqlId = daoInterface.getName() + "." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                System.out.println(sqlCommandType);
                if(sqlCommandType == SqlCommandType.INSERT){<!-- -->
                    methodStr.append("sqlSession.");
                }else if(sqlCommandType == SqlCommandType.DELETE){<!-- -->

                }else if(sqlCommandType == SqlCommandType.UPDATE){<!-- -->
                    methodStr.append("return sqlSession.update("" + sqlId + "", arg0);");
                }else if(sqlCommandType == SqlCommandType.SELECT){<!-- -->
                    methodStr.append("return (" + method.getReturnType().getName() + ")sqlSession.selectOne("" + sqlId + "", arg0);");
                }


                methodStr.append("}");
                System.out.println(methodStr);
                //Method added to class
                ctMethod = CtMethod.make(String.valueOf(methodStr), ctClass);
                ctClass.addMethod(ctMethod);

            } catch (Exception e) {<!-- -->
                e.printStackTrace();
            }
        });
        //Load class
        Object obj = null;
        try {<!-- -->
            Class<?> clazz = ctClass.toClass();
            //Create class
            obj = clazz.newInstance();
        } catch (CannotCompileException e) {<!-- -->
            throw new RuntimeException(e);
        } catch (InstantiationException e) {<!-- -->
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {<!-- -->
            throw new RuntimeException(e);
        }
        return obj;
    }
}