First MyBatis program (XML configuration)
In the previous article, I briefly summarized some limitations of native JDBC, and introduced the MyBatis framework. I sorted out how to build the working environment of MyBatis in detail
In this article, we start our first routine on the basis of setting up a good working environment. However, after we simply let the program run, we will explain how to customize the MyBatis framework. What is the meaning of it?
Although the first routine is relatively simple, there are many points that are easy to cause doubts. For example, why there are builder objects after using the factory mode? Through the custom framework, you can make your understanding of MyBatis deeper and better apply the framework
First of all, we want our first program to run
1. Set up the environment, and specify the location of the mapping configuration file in the main configuration file (SqlMapConfig.xml)
<! -- specify the location of the mapping profile -- > <mappers> <mapper resource="cn/ideal/mapper/UserMapper.xml"/> </mappers>
2. In the test folder, create a structure test class as shown in the figure
Because the method written in our mapper interface is a way to query all information, we can write it directly as shown in the figure below. This is the first routine. Later, we will talk about the points in detail, let our own program run and have a look
public class MyBatisTest { public static void main(String[] args) throws Exception { //Read profile InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //Create SqlSessionFactory factory SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = factoryBuilder.build(in); //Using factories to produce SqlSession objects SqlSession session = factory.openSession(); //Using SqlSession to create a proxy object for Mapper interface UserMapper userMapper = session.getMapper(UserMapper.class); //Using proxy objects to execute methods List<User> users = userMapper.findAllUserInfo(); for (User user : users) { System.out.println(user); } //Release resources session.close(); in.close(); } }
First MyBatis program (annotation configuration)
Annotation configuration. Naturally, our UserMapper.xml can be deleted. At the same time, we need to add annotations to the mapper interface method as shown in the figure
public interface UserMapper { /** * Query all user information * * @return */ @Select("select * from user") List<User> findAllUserInfo(); }
Then in the main configuration file, use the class attribute to specify the annotated mapper fully qualified class name
<mappers> <mapper class="cn.ideal.mapper.UserMapper"/> </mappers>
The results of the two methods are consistent, as shown in the following figure
Customize the MyBatis framework (use XML first)
First, we create a Maven project, modify its pom.xml file to add some necessary coordinates. Because we use dom4j to parse the XML file, we need to introduce dom4j and jaxen
<?xml version="1.0" encoding="UTF-8"?> <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>cn.ideal</groupId> <artifactId>code_02_user_defined</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> </dependencies> </project>
Because today we are going to use the custom MyBatis framework to make a simple query, that is, to query all the User information in the table, we need to create corresponding User class entities according to the database content
CREATE DATABASE ideal_mybatis; -- Create database CREATE TABLE USER ( id INT(11)NOT NULL AUTO_INCREMENT, username VARCHAR(32) NOT NULL COMMENT 'User name', telephone VARCHAR(11) NOT NULL COMMENT 'Mobile phone', birthday DATETIME DEFAULT NULL COMMENT 'Birthday', gender CHAR(1) DEFAULT NULL COMMENT 'Gender', address VARCHAR(256) DEFAULT NULL COMMENT 'address', PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8;
Create the UserMapper interface and write related methods
package cn.ideal.mapper; public interface UserMapper { /** * Query all user information */ List<User> findAllUserInfo(); }
To set the main configuration file (SqlMapConfig.xml), it should be noted that since we need to simulate and design a MyBatis framework ourselves, we should not habitually add the corresponding DTD specification constraints. Here is the specific code
<?xml version="1.0" encoding="UTF-8"?> <!-- mybatis Master profile --> <configuration> <!-- Configuration environment, and spring After integration environments Configuration will be abolished --> <environments default="development"> <environment id="development"> <!-- Use JDBC transaction management --> <transactionManager type="JDBC"></transactionManager> <!-- Database connection pool --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ideal_mybatis"/> <property name="username" value="root"/> <property name="password" value="root99"/> </dataSource> </environment> </environments> <!-- Specify the location of the mapping profile --> <mappers> <mapper resource="cn/ideal/mapper/UserMapper.xml"/> </mappers> </configuration>
Modify its SQL mapping profile
<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.ideal.mapper.UserMapper"> <select id="findAllUserInfo" resultType="cn.ideal.domain.User"> select * from user </select> </mapper>
Test classes, our test classes used today, are consistent with those used directly by MyBatis framework, but we need to implement some of them by ourselves
package cn.ideal.test; import cn.ideal.domain.User; import cn.ideal.mapper.UserMapper; import cn.ideal.mybatis.io.Resources; import cn.ideal.mybatis.sqlsession.SqlSession; import cn.ideal.mybatis.sqlsession.SqlSessionFactory; import cn.ideal.mybatis.sqlsession.SqlSessionFactoryBuilder; import java.io.InputStream; import java.util.List; public class MyBatisTest { public static void main(String[] args) throws Exception { //Read profile InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //Create SqlSessionFactory factory SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = factoryBuilder.build(in); //Using factories to produce SqlSession objects SqlSession session = factory.openSession(); //Using SqlSession to create a proxy object for Mapper interface UserMapper userMapper = session.getMapper(UserMapper.class); //Using proxy objects to execute methods List<User> users = userMapper.findAllUserInfo(); for (User user : users) { System.out.println(user); } //Release resources session.close(); in.close(); } }
Let's refer to the real MyBatis. First, we need to regulate. Resources and SqlSessionFactoryBuilder classes need SqlSessionFactory and SqlSession For these two interfaces, then we need to create their corresponding implementation classes. For the rest, we need to create them again as needed. Then we need to add the corresponding methods to the created classes according to the needs of the test classes
###(1) Resources class
First, create a Resources class, and add a getResourceAsStream method in it. The parameter type is naturally a string type. According to the receiving type in the test class, it is determined that the return value type is InputStream, so you can write the specific method body, as shown in the following figure
package cn.ideal.mybatis.io; import java.io.InputStream; public class Resources { /** * Gets a byte input stream based on the parameters passed in * @param filePath * @return */ public static InputStream getResourceAsStream(String filePath){ // Obtain the bytecode of the current class, the classloader of the bytecode, and read the configuration according to the classloader return Resources.class.getClassLoader().getResourceAsStream(filePath); } }
After getting the byte input stream of the main configuration file, we need to parse the XML. Here we use the XML parsing method of Dom4j. In Pom.xml, we have introduced its coordinates. We need to create its tool class. Of course, we can find a ready-made tool class, or write one manually. Our focus is on MyBatis On the implementation process of
(2) XMLConfigBuilder (parse the XML tool class and understand it)
Because of the way of XML configuration we first used, we don't care about the annotated part of XML for the moment, so we don't care about the annotated part for the moment
package cn.ideal.mybatis.utils; import cn.ideal.mybatis.cfg.Configuration; import cn.ideal.mybatis.cfg.Mapper; import cn.ideal.mybatis.io.Resources; //import com.itheima.mybatis.annotations.Select; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Used to parse configuration files */ public class XMLConfigBuilder { /** * Parse the main configuration file and fill in the contents to the places required by the DefaultSqlSession * Technology used: * dom4j+xpath,So you need to import the coordinates of dom4j and jaxen */ public static Configuration loadConfiguration(InputStream config){ try{ //Define the configuration object that encapsulates the connection information (mybatis configuration object) Configuration cfg = new Configuration(); //1. Get SAXReader object SAXReader reader = new SAXReader(); //2. Get the Document object according to the byte input stream Document document = reader.read(config); //3. Get root node Element root = document.getRootElement(); //4. Use the method of selecting the specified node in xpath to obtain all property nodes List<Element> propertyElements = root.selectNodes("//property"); //5. Traverse nodes for(Element propertyElement : propertyElements){ //Determine which part of the information the node is connecting to the database //Get the value of the name attribute String name = propertyElement.attributeValue("name"); if("driver".equals(name)){ //Presentation driver //Get the value of the property tag value property String driver = propertyElement.attributeValue("value"); cfg.setDriver(driver); } if("url".equals(name)){ //Represents a connection string //Get the value of the property tag value property String url = propertyElement.attributeValue("value"); cfg.setUrl(url); } if("username".equals(name)){ //Represents the user name //Get the value of the property tag value property String username = propertyElement.attributeValue("value"); cfg.setUsername(username); } if("password".equals(name)){ //Express password //Get the value of the property tag value property String password = propertyElement.attributeValue("value"); cfg.setPassword(password); } } //Take out all mapper tags in mappers, and judge whether they use resource or class attribute List<Element> mapperElements = root.selectNodes("//mappers/mapper"); //Ergodic set for(Element mapperElement : mapperElements){ //Determine which attribute mapperElement uses Attribute attribute = mapperElement.attribute("resource"); if(attribute != null){ System.out.println("XML mode"); //Indicates that there is a resource attribute, which is XML //Get the value of the property String mapperPath = attribute.getValue();//Get the value of the attribute "com/itheima/dao/IUserDao.xml" //Get the content of the map configuration file and encapsulate it into a map Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath); //Assign values to mappers in configuration cfg.setMappers(mappers); }else{ // System.out.println("annotation method"); // //Indicates that there is no resource attribute. Annotation is used // //Get the value of the class property // String daoClassPath = mapperElement.attributeValue("class"); // //Obtain the necessary information for encapsulation according to daoClassPath // Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath); // //Assign values to mappers in configuration // cfg.setMappers(mappers); } } //Return to Configuration return cfg; }catch(Exception e){ throw new RuntimeException(e); }finally{ try { config.close(); }catch(Exception e){ e.printStackTrace(); } } } /** * Parses the XML based on the passed in parameters and encapsulates it in the Map * @param mapperPath Location of the mapping profile * @return map It contains the unique ID obtained (key is composed of the fully qualified class name and method name of dao) * And the necessary information for execution (value is a Mapper object, which stores the executed SQL statement and the fully qualified class name of the entity class to be encapsulated) */ private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException { InputStream in = null; try{ //Define return value object Map<String,Mapper> mappers = new HashMap<String,Mapper>(); //1. Get byte input stream according to path in = Resources.getResourceAsStream(mapperPath); //2. Get the Document object according to the byte input stream SAXReader reader = new SAXReader(); Document document = reader.read(in); //3. Get root node Element root = document.getRootElement(); //4. Get the value of the namespace attribute of the root node String namespace = root.attributeValue("namespace");//It is the key part of the map //5. Get all select nodes List<Element> selectElements = root.selectNodes("//select"); //6. Traverse the select node set for(Element selectElement : selectElements){ //Take the value of id attribute to form the key part of map String id = selectElement.attributeValue("id"); //Take the value of the resultType property to form the value part of the map String resultType = selectElement.attributeValue("resultType"); //Extract the text content to form the value part of the map String queryString = selectElement.getText(); //Create Key String key = namespace+"."+id; //Create Value Mapper mapper = new Mapper(); mapper.setQueryString(queryString); mapper.setResultType(resultType); //Store key and value in mappers mappers.put(key,mapper); } return mappers; }catch(Exception e){ throw new RuntimeException(e); }finally{ in.close(); } } /** * According to the parameters passed in, we get all the methods annotated by the select annotation in dao. * According to the method name and class name, as well as the value of the annotated value attribute on the method, the necessary information of Mapper is formed * @param daoClassPath * @return */ // private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{ // //Define return value object // Map<String,Mapper> mappers = new HashMap<String, Mapper>(); // // //1. Get bytecode object of dao interface // Class daoClass = Class.forName(daoClassPath); // //2. Get the method array in dao interface // Method[] methods = daoClass.getMethods(); // //3. Traverse Method array // for(Method method : methods){ // //Take out each method and judge whether there is a select annotation // boolean isAnnotated = method.isAnnotationPresent(Select.class); // if(isAnnotated){ // //Create Mapper object // Mapper mapper = new Mapper(); // //Get the value attribute value of the annotation // Select selectAnno = method.getAnnotation(Select.class); // String queryString = selectAnno.value(); // mapper.setQueryString(queryString); // //Gets the return value of the current method, which also requires generic information // Type type = method.getGenericReturnType();//List<User> // //Judge whether type is a parameterized type // if(type instanceof ParameterizedType){ // / / strong turn // ParameterizedType ptype = (ParameterizedType)type; // //Get the actual type parameters in the parameterized type // Type[] types = ptype.getActualTypeArguments(); // //Take out the first one // Class domainClass = (Class)types[0]; // //Get the class name of domainClass // String resultType = domainClass.getName(); // //Assign value to Mapper // mapper.setResultType(resultType); // } // //Assembly key information // //Get the name of the method // String methodName = method.getName(); // String className = method.getDeclaringClass().getName(); // String key = className+"."+methodName; // //Assign map // mappers.put(key,mapper); // } // } // return mappers; // } }
Let's take a look at the code. First, get all the property nodes, get the values of drivers, user names, passwords, etc., and then
Take out all mapper tags in mappers, judge whether they use resource or class attribute, and then judge whether they use XML or annotation. For example, the XML method in this example will get the main configuration file < mapper resource = "cn/ideal/mapper/UserMapper.xml" / > in this sentence, cn/ideal/mapper/UserMapper.xml will be used to solve the SQL mapping configuration file Analysis, also extract some necessary information
However, if we want to use this tool class, we can see that there are still some errors reported. This is because we lack some necessary classes. We need to supplement them ourselves
First, we need to create a Configuration entity class to pass some link information we get
public class Configuration { private String driver; private String url; private String username; private String password; private Map<String, Mapper> mappers = new HashMap<String, Mapper>(); //Supplement its corresponding get set method //Special note: setMappers need to use the append write method of putALL. It cannot be assigned directly. Otherwise, the old one will be overwritten by the new one public void setMappers(Map<String, Mapper> mappers) { this.mappers.putAll(mappers); // How to add } }
Secondly, we can see that when we parse the SQL mapping file, we encapsulate it into a Map collection, where the key is composed of the Mapper's fully qualified class name and method name. That is to say, we obtain the values of the two respectively, and then splice the strings into the key. The value is a Mapper object, its key is id, and the value is SQL statement and resultType
public class Mapper { private String queryString; //SQL private String resultType; / / fully qualified class name of entity class Supplement its corresponding get set method }
(3) SqlSessionFactoryBuilder class
We continue to return to the test class. We need to create a SqlSessionFactoryBuilder class. According to the test class, we pass the stream file obtained from the resource class as a parameter to the build method in this class. That is to say, in the build method, we need to parse the XML, and then use the Configuration class just created to receive it But according to the test class, we can know that the SqlSessionFactory type is used in the test to receive the build return. From the real MyBatis, we can see that SqlSessionFactory is an interface, and the builder design pattern is used here, so what we really want to return is its corresponding implementation class, and the Configuration object is passed in. The code is as follows: As shown below,
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream config){ Configuration cfg = XMLConfigBuilder.loadConfiguration(config); return new DefaultSqlSessionFactory(cfg); } }
(4) SqlSessionFactory interface
public interface SqlSessionFactory { /** * Used to open a new SqlSession object * @return */ SqlSession openSession(); }
(5) DefaultSqlSessionFactor implementation class
Because we have passed Configuration into the method, so it is necessary to create a Configuration member first, then a structure with parameters, and also create its openSession method according to the test class to create objects for operating data. Here is the same idea. SqlSession is an interface, so we really need to return or its interface
public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration cfg; public DefaultSqlSessionFactory(Configuration cfg) { this.cfg = cfg; } /** * Used to create a new operation database object * @return */ public SqlSession openSession() { return new DefaultSqlSession(cfg); } }
(6) SqlSession interface
Here we need to use SqlSession to create the proxy object of Mapper interface
public interface SqlSession { /** * Create a proxy object based on parameters * @param mapperInterfaceClass mapper Interface bytecode of * @param <T> * @return */ <T> T getMapper(Class<T> mapperInterfaceClass); /** * Release resources */ void close(); }
(7) DefaultSqlSession implementation class
First of all, we still need to create a member and a construction method with parameters. We need to create a tool class to create a data source named DataSourceUtil. At the same time, the more important thing in this class is to create our getMapper method. We use the method Proxy.newProxyInstance to create the parameters,
- The first parameter is the classloader we need to proxy, that is, the classloader of the proxy
- The second parameter is the interface that the dynamic proxy class needs to implement
- When the third parameter dynamic proxy method is executed, it will call the method inside to execute (self created)
- The parameter is to pass in the information in our configuration
public class DefaultSqlSession implements SqlSession { private Configuration cfg; private Connection connection; public DefaultSqlSession(Configuration cfg) { this.cfg = cfg; connection = DataSourceUtil.getConnection(cfg); } /** * Used to create proxy objects * * @param mapperInterfaceClass mapper Interface bytecode of * @param <T> * @return */ public <T> T getMapper(Class<T> mapperInterfaceClass) { return (T) Proxy.newProxyInstance(mapperInterfaceClass.getClassLoader(), new Class[]{mapperInterfaceClass},new MapperProxy(cfg.getMappers(),connection)); } /** * Used to release resources */ public void close() { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
(8) DataSourceUtil tool class
public class DataSourceUtil { public static Connection getConnection(Configuration cfg){ try { Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword()); } catch (Exception e) { throw new RuntimeException(e); } } }
(9) MapperProxy
We started to implement the MapperProxy class we just customized, the regular creation members and constructors. The reason why we passed in Connection is that for the final execution of Executor(), another important thing of this class is to use invoke to enhance the methods, get and combine them,
public class MapperProxy implements InvocationHandler { //The key of map is fully qualified class name + method name private Map<String, Mapper> mappers; private Connection connection; public MapperProxy(Map<String, Mapper> mappers,Connection connection) { this.mappers = mappers; this.connection = connection; } /** * Used to enhance methods. The enhancement here is to call the selectList method * @param proxy * @param method * @param args * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //Get method name String methodName = method.getName(); //Get the name of the class where the method is located String className = method.getDeclaringClass().getName(); //Combinatorial key String key = className + "." + methodName; //Get Mapper object in mappers Mapper mapper = mappers.get(key); //Judge whether there is mapper if (mapper == null){ throw new IllegalArgumentException("Wrong parameter passed in"); } //Call tool class to query all 1 return new Executor().selectList(mapper,connection); } }
(10) Executor tool class
Then we use an off the shelf tool class to execute our SQL, because the SQL statement and resultType have been encapsulated in mapper and passed in
/** * Responsible for executing SQL statements and encapsulating the result set */ public class Executor { public <E> List<E> selectList(Mapper mapper, Connection conn) { PreparedStatement pstm = null; ResultSet rs = null; try { //1. Take out the data in mapper String queryString = mapper.getQueryString();//select * from user String resultType = mapper.getResultType();//com.itheima.domain.User Class domainClass = Class.forName(resultType); //2. Get PreparedStatement object pstm = conn.prepareStatement(queryString); //3. Execute SQL statement to get result set rs = pstm.executeQuery(); //4. Encapsulation result set List<E> list = new ArrayList<E>();//Define return value while(rs.next()) { //Instantiate the entity class object to encapsulate E obj = (E)domainClass.newInstance(); //Get meta information of result set: ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //Take out the total number of columns int columnCount = rsmd.getColumnCount(); //Total number of traversal columns for (int i = 1; i <= columnCount; i++) { //Get the name of each column. The sequence number of the column name starts from 1 String columnName = rsmd.getColumnName(i); //Get the value of each column according to the column name Object columnValue = rs.getObject(columnName); //Assign value to obj: use Java introspection mechanism (implement property encapsulation with PropertyDescriptor) PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//Requirement: the attribute of entity class and the column name of database table should be the same //Get its write method Method writeMethod = pd.getWriteMethod(); //Assign the value of the obtained column to the object writeMethod.invoke(obj,columnValue); } //Adding a valued object to a collection list.add(obj); } return list; } catch (Exception e) { throw new RuntimeException(e); } finally { release(pstm,rs); } } private void release(PreparedStatement pstm,ResultSet rs){ if(rs != null){ try { rs.close(); }catch(Exception e){ e.printStackTrace(); } } if(pstm != null){ try { pstm.close(); }catch(Exception e){ e.printStackTrace(); } } } }
We can test it here. There is no problem with the result
How to define MyBatis by annotation
First, we need to change it to the form of annotation in the main configuration file, namely
<mappers> <mapper class="cn.ideal.mapper.UserMapper"/> </mappers>
Then create a custom Select annotation
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Select { /** * Configure the * @return */ String value(); }
Then add annotation to our UserMapper interface method, and write the SQL statement in it
public interface UserMapper { /** * Query all user information * * @return */ @Select("select * from user") List<User> findAllUserInfo(); }
Finally, we will uncomment the part of XMLConfigBuilder tool class that has been commented out
The operation results are as follows:
Finally, I will give you the program structure chart, which is convenient for you to create a package. The structure is cn.ideal.xxxx
summary
I'll get back to it. The whole process
- The configuration file related to MyBatis, SqlMapConfig.xml in the example is read by the method in the Rosource class to get a stream file
- In SqlSessionFactoryBuilder, the Configuration file is parsed by XMLConfigBuilder. At the same time, the Configuration class is used to access and extract some specific Configuration information
- Create a new operation database object through SqlSessionFactory
- Get SqlSession
- Using MapperProxy to execute SQL is essentially to call Executor executor
- test run
Make a simple flow chart
Note: the code of this article comes from a certain horse. I reorganized and summarized it according to my thinking. Of course, it's not a copy. I also typed it once and simply explained and analyzed it. Naturally, there is no advanced technology to speak of. Maybe it can help some friends. Anyway, thanks for the support. If there are friends who need the original code, I will More links for your reference
Ending
If there are any shortcomings or mistakes in the article, please leave a message and share your ideas. Thank you for your support!
If you can help, then pay attention to me! If you prefer the way of reading WeChat articles, you can pay attention to my public number.
We don't know each other here, but we are working hard for our dreams
A public figure that persists in pushing original development technology articles: ideal two days