preface
This paper is divided into two parts: the first part writes an introductory program with mybatis, and the second part removes mybatis from the introductory program and replaces it with a custom frameworkTip: the following is the main content of this article
1, Introduction to Mybatis framework
1.1. Import tables and data
DROP TABLE IF EXISTS USER; CREATE TABLE USER ( id INT(11) NOT NULL AUTO_INCREMENT, username VARCHAR(32) NOT NULL COMMENT 'User name', birthday DATETIME DEFAULT NULL COMMENT 'birthday', sex CHAR(1) DEFAULT NULL COMMENT 'nature`user`other', address VARCHAR(256) DEFAULT NULL COMMENT 'address', PRIMARY KEY (id) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO USER(id,username,birthday,sex,address) VALUES (41,'Lao Wang','2018-02-27 17:47:08','male','Beijing'),(42,'Lao Chen','2018-02-27 17:47:08','male','Beijing'),(43,'Lao Liang','2018-02-27 17:47:08','male','Beijing'); DROP TABLE IF EXISTS account; CREATE TABLE account ( accountId INT(11) NOT NULL AUTO_INCREMENT, UID INT(11) DEFAULT NULL COMMENT 'User number', MONEY DOUBLE DEFAULT NULL COMMENT 'amount of money', PRIMARY KEY (accountId), KEY FK_Reference_8 (UID), CONSTRAINT FK_Reference_8 FOREIGN KEY (UID) REFERENCES USER (id) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO account(accountId,UID,MONEY) VALUES (1,41,1000),(2,42,1000),(3,43,2000);
1.2. Prepare database environment, create projects and add dependencies
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mymybatis</groupId> <artifactId>mybatis_my</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!-- mybatis rely on --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project>
1.3,SqlMapConfig.xml configuration
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- Database connection information --> <!-- environments Configure all database connections default:Default connection information environment: Configure a database connection information id:Unique tag transactionManager: Transaction manager type:Transaction manager type jdbc:use mybatis Built in transaction manager Connection.setAutoCommit(false) Connection.commit() Connection.rollback() dataSource:data source(Connection pool) type:Data source type pooled:use mybatis Self contained data source --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="jdbc"></transactionManager> <dataSource type="pooled"> <property name="url" value="jdbc:mysql://192.168.211.130:3306/mybatis_demo"/> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- relevant Dao Interface mapping(File or class) --> <mappers> <mapper resource="com/mymybatis/dao/UserDao.xml"/> </mappers> </configuration>
1.4 entity class
package com.mymybatis.pojo; import java.util.Date; public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address=" + address + "]"; } }
1.5. Write UserDao interface and UserDao interface mapping file
package com.mymybatis.dao; import java.util.List; import com.mymybatis.pojo.User; public interface UserDao { List<User> findAll(); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mymybatis.dao.UserDao"> <select id="findAll" resultType="com.mymybatis.pojo.User"> select * from user; </select> </mapper>
1.6 test
package com.mymybatis.test; import java.io.InputStream; import java.util.List; 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 org.junit.Test; import com.mymybatis.dao.UserDao; import com.mymybatis.pojo.User; public class Demo { @Test public void test1() throws Exception{ //1. Load sqlmapconfig xml InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. Create SQLSessionFactory factory SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3. Create SQLSession SqlSession sqlSession = factory.openSession(); //4. Generate Dao interface proxy object UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.findAll(); for (User user : userList) { System.out.println(user); } sqlSession.close(); in.close(); } }
2, Custom implementation of Mybatis framework
2.1. Copy the introductory program just now
2.2. User defined framework environment preparation
Technical points used
- JDK dynamic agent
- Parsing xml with dom4j+xpath
- jdbc
- jdbc metadata (encapsulating database result set)
- reflex
2.3 remove the dependency of mybatis and add a new dependency
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mymybatis</groupId> <artifactId>mybatis_my</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!--mysql drive --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!-- xpath --> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> <!-- druid data source --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> </dependencies> </project>
2.4. Modify test class
package com.mymybatis.test; import java.util.List; import org.junit.Test; import com.mymybatis.dao.UserDao; import com.mymybatis.pojo.User; public class Demo { @Test public void test1() throws Exception{ //1. SQLSessionFactory creation SqlSessionFactory factory = new SqlSessionFactory(); //2. Create SQLSession SqlSession sqlSession = factory.openSession(); //3. Generate Dao interface proxy object UserDao userDao = sqlSession.getMapper(UserDao.class); System.out.println("Proxy object"+userDao.getClass()); List<User> userList = userDao.findAll(); for (User user : userList) { System.out.println(user); } } }
Pay attention to the error report here. Let's ignore it first
2.5 user defined framework analysis
To analyze the test code, we need to implement the following steps
- Defines the SqlSessionFactory class and provides the openSession method
- Define SqlSession interface, DefaultSqlSession implementation class and getMapper method
- In the openSession method of SqlSessionFactory, return the implementation object of the SqlSession interface (DefaultSqlSession)
2.6 user defined framework (II) defining core APIs
package com.mymybatis.mybatis.factory; public class SqlSessionFactory { /** * Create SqlSession implementation object */ public void openSession() { } }
2.7. Define SqlSession interface, DefaultSqlSession implementation class and getMapper method
package com.mymybatis.mybatis.session; public interface SqlSession { /** * Define getMapper to generate Dao interface proxy object * Parameter: pass in the dao interface type to be generated * Return value: the proxy object is finally generated */ public <T> T getMapper(Class<T> daoClass); }
Defines the implementation of the SqlSession interface
package com.mymybatis.mybatis.session.impl; import com.mymybatis.mybatis.session.SqlSession; /** * SqlSession Default implementation of interface * @author 16427 * */ public class DefaultSqlSession implements SqlSession{ @Override public <T> T getMapper(Class<T> daoClass) { return null; } }
2.8 in the openSession method of SqlSessionFactory, return the implementation object of SqlSession interface
package com.mymybatis.mybatis.factory; import com.mymybatis.mybatis.session.SqlSession; import com.mymybatis.mybatis.session.impl.DefaultSqlSession; public class SqlSessionFactory { /** * Create SqlSession implementation object */ public SqlSession openSession() { return new DefaultSqlSession(); } }
2.9 after defining the above Api, the test code will not report an error, but it will report an error when running
Cause: the getMapper method of the SqlSession interface implementation class (DefaultSqlSession) did not return a proxy object
2.10 customize the framework to improve DefaultSqlSession
Let getMapper of SqlSession generate JDK proxy object
package com.mymybatis.mybatis.session.impl; import java.lang.reflect.Proxy; import com.mymybatis.mybatis.proxy.MapperProxy; import com.mymybatis.mybatis.session.SqlSession; /** * SqlSession Default implementation of interface * @author 16427 * */ public class DefaultSqlSession implements SqlSession{ /** * Use JDK dynamic proxy to generate interface proxy object (interface proxy) * Parameter 1: class loader, which usually provides the class loader of the current class * Parameter 2: incoming interface list * Parameter 3: InvocationHandler interface implementation class, which can provide anonymous internal class implementation */ @Override public <T> T getMapper(Class<T> daoClass) { return (T)Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[] {daoClass}, new MapperProxy()); } }
The implementation of InvocationHandler interface and MapperProxy class are extracted
package com.mymybatis.mybatis.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * InvocationHandler Implementation class of interface * @author 16427 * */ public class MapperProxy implements InvocationHandler{ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("========="+method.getName()); return null; } }
Execute test class
2.11 customize the framework and improve MapperProxy A to define Configuration and Mapper classes
2.11.1 Configuration is designed to complete the loading of all configurations
public class Configuration { //Define variables to store database connection parameters private String url; private String driver; private String username; private String password; //Defines all method information used to store the entire project /** * key: namespace+.+id com.itheima.dao.UserDao.findAll * value: Information about the entire method */ private Map<String,Mapper> mappers = new HashMap<>(); }
At the same time, a Mapper is designed to encapsulate each method information
package com.mymybatis.mybatis.config; /** * This class is used for * @author 16427 * */ public class Mapper { private String id; private String resultType; private String sql; private String namespace; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } }
Continue to supplement the logic of Configuration and complete sqlmapconfig XML and mapping file loading
package com.mymybatis.mybatis.config; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * This class encapsulates all XML configuration files for the project * @author 16427 * */ public class Configuration { //Define variables to store database connection parameters private String url; private String driver; private String username; private String password; //Defines all method information used to store the entire project private Map<String,Mapper> mappers=new HashMap<>(); //Define a configuration static object private static Configuration configuration=null; //The private constructor uses single column mode private Configuration() { loadSqlMapConfig(); } public static Configuration getConfiguration() { if(configuration==null) { return new Configuration(); }else { return configuration; } } /** * Read sqlmapconfig XML (the singleton mode is read only once) */ private void loadSqlMapConfig() { //Read xml configuration using Dom4j+xpath //1. Create a SaxReader SAXReader saxReader = new SAXReader(); //2. Load XML and return Document object InputStream in = Configuration.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"); try { Document documnet= saxReader.read(in); //3. Get root tag Element rootElement = documnet.getRootElement(); /*4.Continue reading word labels Read the property tag (it's easier to get the tag using xpath) / Level by level acquisition // Get the specified label directly */ List<Element> propElements=rootElement.selectNodes("//property"); for (Element propElement : propElements) { String name=propElement.attributeValue("name"); String value = propElement.attributeValue("value"); if("url".equals(name)) { this.url=value; } if("driver".equals(name)) { this.driver=value; } if("username".equals(name)) { this.username=value; } if("password".equals(name)) { this.password=value; } } //Continue reading sub Tags List<Element> mapperElements=rootElement.selectNodes("//mapper"); for (Element mapperElement : mapperElements) { String mapperResource = mapperElement.attributeValue("resource"); //Load dao interface configuration file loadMapper(mapperResource); } }catch (Exception e) { e.printStackTrace(); } } private void loadMapper(String mapperResource) { //Read xml configuration using Dom4j+xpath //Create SAXReader SAXReader saxReader = new SAXReader(); //Loading xml returns du InputStream in = Configuration.class.getClassLoader().getResourceAsStream(mapperResource); try { Document document = saxReader.read(in); //Get root label Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> selectElement = rootElement.selectNodes("select");//Ignore the write operation first for (Element element : selectElement) { Mapper mapper = new Mapper(); String id=element.attributeValue("id"); String resultType = element.attributeValue("resultType"); String sql = element.getTextTrim(); mapper.setId(id); mapper.setResultType(resultType); mapper.setSql(sql); mapper.setNamespace(namespace); //Save to Map collection //namespace+id = com.itheima.dao.UserDao.findAll mappers.put(namespace+"."+id, mapper); } }catch (Exception e) { e.printStackTrace(); } } }
2.12. Customize the framework, improve MapperProxy, and define the Executor class
2.12.1 in the Configuration class, provide methods to obtain method mapping information and connect pool objects
package com.mymybatis.mybatis.config; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.alibaba.druid.pool.DruidDataSource; /** * This class encapsulates all XML configuration files for the project * @author 16427 * */ public class Configuration { //Define variables to store database connection parameters private String url; private String driver; private String username; private String password; //Define connection pool objects private DataSource dataSource; //Defines all method information used to store the entire project private Map<String,Mapper> mappers=new HashMap<>(); //Define a configuration static object private static Configuration configuration=null; //The private constructor uses single column mode private Configuration() { loadSqlMapConfig(); } public static Configuration getConfiguration() { if(configuration==null) { return new Configuration(); }else { return configuration; } } /** * Read sqlmapconfig XML (the singleton mode is read only once) */ private void loadSqlMapConfig() { //Read xml configuration using Dom4j+xpath //1. Create a SaxReader SAXReader saxReader = new SAXReader(); //2. Load XML and return Document object InputStream in = Configuration.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"); try { Document documnet= saxReader.read(in); //3. Get root tag Element rootElement = documnet.getRootElement(); /*4.Continue reading word labels Read the property tag (it's easier to get the tag using xpath) / Level by level acquisition // Get the specified label directly */ List<Element> propElements=rootElement.selectNodes("//property"); for (Element propElement : propElements) { String name=propElement.attributeValue("name"); String value = propElement.attributeValue("value"); if("url".equals(name)) { this.url=value; } if("driver".equals(name)) { this.driver=value; } if("username".equals(name)) { this.username=value; } if("password".equals(name)) { this.password=value; } } //Continue reading sub Tags List<Element> mapperElements=rootElement.selectNodes("//mapper"); for (Element mapperElement : mapperElements) { String mapperResource = mapperElement.attributeValue("resource"); //Load dao interface configuration file loadMapper(mapperResource); } }catch (Exception e) { e.printStackTrace(); } } private void loadMapper(String mapperResource) { //Read xml configuration using Dom4j+xpath //Create SAXReader SAXReader saxReader = new SAXReader(); //Loading xml returns du InputStream in = Configuration.class.getClassLoader().getResourceAsStream(mapperResource); try { Document document = saxReader.read(in); //Get root label Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> selectElement = rootElement.selectNodes("select");//Ignore the write operation first for (Element element : selectElement) { Mapper mapper = new Mapper(); String id=element.attributeValue("id"); String resultType = element.attributeValue("resultType"); String sql = element.getTextTrim(); mapper.setId(id); mapper.setResultType(resultType); mapper.setSql(sql); mapper.setNamespace(namespace); //Save to Map collection //namespace+id = com.itheima.dao.UserDao.findAll mappers.put(namespace+"."+id, mapper); } }catch (Exception e) { e.printStackTrace(); } } //You don't need to provide connection information, you just need to provide connection pool objects public DataSource getDataSource() { if(dataSource==null) { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setUrl(url); dataSource.setPassword(password); dataSource.setDriverClassName(driver); this.dataSource=dataSource; } return dataSource; } }
2.12.2 design the Executor class and provide the selectList method to complete the final database query and result encapsulation
package com.mymybatis.mybatis.util; import java.util.List; import javax.sql.DataSource; import com.mymybatis.mybatis.config.Mapper; /** * This class is used for * 1)Execute sql statements using JDBC * 2)Encapsulate the execution result level of SQL statement into object return * @author 16427 * */ public class Executor { /** * Query multiple records * @param mapper Matching to method mapping information through XML configuration (including executed SQL statements) * @param dataSource Objects required to execute sql statements * @return */ public static List selectList(Mapper mapper,DataSource dataSource) { return null; } }
2.12.3. Complete the core logic in MapperProxy
package com.mymybatis.mybatis.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import javax.sql.DataSource; import com.mymybatis.mybatis.config.Configuration; import com.mymybatis.mybatis.config.Mapper; import com.mymybatis.mybatis.util.Executor; /** * InvocationHandler Implementation class of interface * */ public class MapperProxy implements InvocationHandler{ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1. Read xml configuration file Configuration configuration = Configuration.getConfiguration(); //2. Get the JAVA object encapsulated with XML information Map<String, Mapper> mappers = configuration.getMappers(); DataSource dataSource = configuration.getDataSource(); //3. Obtain the sql statement to be executed from the configuration information //3.1 get the method of the currently executed interface //Method name String methodName = method.getName(); //Interface type of method String interName = method.getDeclaringClass().getName(); Mapper mapper = mappers.get(interName+"."+methodName); if(mapper==null) { throw new RuntimeException("The method is not in XML Mapping"); } //4. Execute SQL statement to obtain the result set //5. Encapsulate the result set into java objects return Executor.selectList(mapper, dataSource); } }
package com.mymybatis.mybatis.util; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import com.mymybatis.mybatis.config.Mapper; /** * This class is used for * 1)Execute sql statements using JDBC * 2)Encapsulate the execution result level of SQL statement into object return * @author 16427 * */ public class Executor { /** * Query multiple records * @param mapper Matching to method mapping information through XML configuration (including executed SQL statements) * @param dataSource Objects required to execute sql statements * @return */ public static List selectList(Mapper mapper,DataSource dataSource) { //Get the sql to be executed String sql = mapper.getSql(); //Get the object that needs to encapsulate each record String resultType = mapper.getResultType();// com.itheima.pojo.User //A list is designed to store the results of all queries List list=new ArrayList<>(); try { //Get connection Connection connection = dataSource.getConnection(); //Prepare sql statements PreparedStatement stmt = connection.prepareStatement(sql); //Execute sql ResultSet rs = stmt.executeQuery(); //Get bytecode object Class clazz = Class.forName(resultType); //Get jdbc metadata ResultSetMetaData metaData = rs.getMetaData(); //Get the number of fields through metadata Integer columnCount = metaData.getColumnCount(); //Encapsulate result sets into result sets while (rs.next()) { //Create an object for each record that needs to be encapsulated Object obj = clazz.newInstance(); //Pass the field value of each record into the object /** * Get the attribute name of each object and each field value of each record (corresponding association: the attribute name and field name are consistent) */ for(int i=1;i<=columnCount;i++) { //Subscripts start with 1 //Get each field name with metadata String columnName = metaData.getColumnName(i); //Each field value Object columnValue = rs.getObject(i); //Use reflection to assign a value to the username attribute of the obj object Field field = clazz.getDeclaredField(columnName); field.setAccessible(true); /** * Object assigned to parameter 1 * Value to be given for parameter 2 * */ field.set(obj, columnValue); } //2.3 store each encapsulated object into the list set list.add(obj); } }catch (Exception e) { e.printStackTrace(); } return list; } }