Recently, I studied the underlying code of Mybatis, and prepared to write a small tool to operate the database, which realized some functions of Mybatis.
1. SQL statements are configured in mapper.xml.
2. Support input of int, String and custom data type.
3. Create the agent implementation object of interface dynamically according to mapper.xml.
Limited functions, the purpose is to clarify the MyBatis framework's underlying ideas, learn more about the implementation of excellent framework ideas, to enhance their coding ability is of great benefit.
The core technology used by the gadget: xml parsing + reflection + jdk dynamic proxy
Next, step by step.
First, why use jdk dynamic proxy?
Traditional development methods:
- Interfaces define business methods.
- Implementing classes to implement business methods.
- Implementing class objects by instantiation to complete business operations.
Mybatis's way:
- Developers only need to create interfaces and define business methods.
- There is no need to create implementation classes.
- Specific business operations are accomplished by configuring xml.
MyBatis's approach eliminates the creation of implementation classes and uses xml to define specific implementations of business methods.
So here comes the question....
We know that Java is an object-oriented programming language. When a program executes business methods at run time, it must have instantiated objects. However, interfaces cannot be instantiated, and there is no implementation class for interfaces. Where does this object come from at this time?
The proxy object is created dynamically when the program is running.
So we need to use JDK dynamic proxy, run-time interface and mapper.xml to dynamically create a proxy object, the program calls the proxy object method to complete business.
Dynamic Agent Reference Dynamic Agent
How to use jdk dynamic proxy?
Create a class and implement InvocationHandler interface. This class has the function of creating dynamic proxy objects. Of course, since it is related to database processing, database connection pool will be used naturally. The realization of Library connection pool can be referred to_ Implementing a mini version of database connection pool by oneself;
My idea is to connect the database directly with the druid database connection pool, and then mainly parse the Mapper.xml file, because in MyBatis, the data in config.xml is mainly used for its own database connection pool. Here we have used the connection pool, so we just need to parse the Mapper.xml file. Data, I tried, xml file parsing is still more troublesome Parsing XML files using SAX But our main logic lies in the implementation of dynamic proxy, so I will replace this xml file with txt file, which is not the key point anyway.
Maybe it will take many days,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Alas, I'm so mad. When we write our own JDK dynamic proxy, we need a public interface, a real object and then dynamically generate the proxy object through the dynamic proxy class. But I looked at the source code of MyBatis, which I really don't understand. We only need a public interface, because it needs in the code. This is the only parameter that is passed. Where is the real object? I read that the real executor is an implementation class of an interface called Execute. How does this implementation class inherit our custom interface? Can a created class dynamically inherit an interface? Even if it inherits dynamically, does it need to dynamically implement the customization method under our custom interface? Oh my god?
Looks like a framework is called a framework,,,,,,,,,, I wrote a pseudo MyBatis, which should be a simple use of JDK dynamic proxy, I customize the implementation of the real object, the following is the code:
druid dependency:
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> <scope>compile</scope> </dependency>
Druid configuration file, druid.properties:
#Driving Path driver=com.mysql.jdbc.Driver #JDBC Connection URL url=jdbc:mysql://127.0.0.1:3306/test4?useSSL=false #Account number username=root #Password password=123456 #Initial connection pool size initPoolSize=10 #Maximum idle time maxIdleTime=20 #Maximum number of connection pools maxPoolSize=40
Database creation:
create table teacher ( tid int(20) not null primary key, tname varchar(20) null );
A few random data inserts, this is my:
Table corresponding entity class POJO:
/** * @ClassName Teacher * @Description teacher Entity classes corresponding to tables * @Author lzq * @Date 2019/8/2 14:40 * @Version 1.0 **/ public class Teacher { private int tid; private String tname; public Teacher(int tid,String tname) { this.tid = tid; this.tname = tname; } public int getTid() { return tid; } public void setTid(int tid) { this.tid = tid; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } @Override public String toString() { return "Teacher{" + "tid=" + tid + ", tname='" + tname + '\'' + '}'; } }
Get the class of database connection pool connection:
import com.alibaba.druid.pool.DruidDataSource; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @ClassName ConnectionTest * @Description Get a connection to the database connection pool * @Author lzq * @Date 2019/8/2 14:32 * @Version 1.0 **/ public class ConnectionTest { public static Connection getConection() throws IOException, SQLException { DruidDataSource dataSource = new DruidDataSource(); //Read configuration information Properties pro = new Properties(); pro.load(ConnectionTest.class.getClassLoader ().getResourceAsStream("druid.properties")); dataSource.setDriverClassName(pro.getProperty("driver")); dataSource.setUrl(pro.getProperty("url")); dataSource.setUsername(pro.getProperty("username")); dataSource.setPassword(pro.getProperty("password")); //Number of Initialized Connections dataSource.setInitialSize(Integer.parseInt(pro.getProperty("initPoolSize"))); //Configure connection waiting timeout dataSource.setMaxWait(Long.parseLong(pro.getProperty("maxIdleTime"))); //Maximum number of concurrent connections dataSource.setMaxActive(Integer.parseInt(pro.getProperty("maxPoolSize"))); Connection connection = dataSource.getConnection(); return connection; } }
Classes that read SQL statement files:
/** * @ClassName InputSql * @Description Read SQL statements * @Author lzq * @Date 2019/8/2 14:19 * @Version 1.0 **/ public class InputSql { public static String getSql(String path) { File file = new File(path); StringBuilder stringBuilder = new StringBuilder(); try { Scanner scanner = new Scanner(file); String temp = null; while (scanner.hasNextLine()) { temp = scanner.nextLine(); // System.out.println(temp); stringBuilder.append(temp); } } catch (FileNotFoundException e) { e.printStackTrace(); } return stringBuilder.toString(); } }
Public interface:
/** * @ClassName TeacherMapper * @Description Interfaces * @Author lzq * @Date 2019/8/2 14:39 * @Version 1.0 **/ public interface TeacherMapper { public Teacher getTeacherId(int id); }
Actuator, real object:
import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName Execute * @Description Actuator, Real Object * @Author lzq * @Date 2019/8/2 14:41 * @Version 1.0 **/ public class Execute implements TeacherMapper{ private String path = ""; public Execute(String path) { this.path = path; } @Override public Teacher getTeacherId(int id) { Connection conection = null; Teacher teacher = null; try { conection = ConnectionTest.getConection(); String sql = InputSql.getSql(this.path)+"?"; PreparedStatement preparedStatement = conection.prepareStatement(sql); preparedStatement.setObject(1,id); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { teacher = new Teacher(resultSet.getInt(1), resultSet.getString(2)); } } catch (IOException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { if(conection != null) { try { conection.close(); } catch (SQLException e) { e.printStackTrace(); } } } return teacher; } }
Generate the proxy object and let the real object execute the corresponding method:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @ClassName ProxyExample * @Description Generating proxy objects * @Author lzq * @Date 2019/8/2 15:17 * @Version 1.0 **/ public class ProxyExample implements InvocationHandler { private Object object = null; //Real object public Object bind(Object o) { this.object = o; return Proxy.newProxyInstance(object.getClass().getClassLoader(),o.getClass().getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o = method.invoke(object,args); return o; } }
Test class:
My file for storing SQL statements:
/** * @ClassName MyTest * @Description Test class * @Author lzq * @Date 2019/8/2 17:34 * @Version 1.0 **/ public class MyTest { public static void main(String[] args) { ProxyExample proxyExample = new ProxyExample(); TeacherMapper teacherMapper = (TeacherMapper)proxyExample.bind(new Execute("E:\\FHB\\io\\sql.txt")); Teacher teacherId = teacherMapper.getTeacherId(2); System.out.println(teacherId); } }
Operation results: