The preface
Hello, everyone, now the holiday, at home idle to prepare a MyBatis framework, now progress: just finished writing select statement query support, including annotations and configuration file form, we have any good comments or do not understand the place welcome message discussion.
What is Mybatis
I believe that JAVA learning partners, have come into contact with JDBC, JDBC provides us with JAVA operation database interface, but because the JDBC API is too cumbersome, we have to do a lot of the same operation every time, and the execution of SQL statements in the process of all kinds of exceptions and resource release processing. There is very little code that actually involves business functions, which obviously affects the efficiency of our development.
So a series of frameworks were born, starting with Hibernate, followed by jdbcTemplate, springDataJpa(based on Hibernate), myBatis, and so on.
Let’s take myBatis and Hibernate as examples. MyBatis is a semi-ORM framework, while Hibernate is a full ORM framework. The so-called ORM is actually object mapping, that is, mapping the tables in our database into JAVA entity classes. Each attribute in the entity class corresponds to each field in the table. Compared with Hibernate, in fact, Mybatis is more suitable for daily development, because we can write SQL by ourselves with Mybatis, so we can optimize SQL to improve performance.
The Mybatis APi
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession sqlSession = factory.openSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> all = mapper.findAll();
for (User xx:all){
System.out.println(xx);
}
sqlSession.close();
in.close();
Copy the code
I believe you are not unfamiliar with the above code, this is the basic APi operation Mybatis, then we start from here handwritten it.
Gets the input stream for the master configuration file
You need to write the Resources utility class
Public class Resources {public static InputStream getResourceAsStream(String path){return Resources.class.getClassLoader().getResourceAsStream(path); }}Copy the code
Write the SqlSessionFactoryBuilder object
Public class SqlSessionFactoryBuilder {public SqlSessionFactoryBuild (InputStream in){// Encapsulates the information in the Configuration file con = XMLConfigBuilder.loadConfiguration(in); Return new DefalutSqlSessionFactroy(con); // Return sqlSessionFactory with configuration information. }}Copy the code
The build method passes in the input stream for the main Configuration class, and we need to parse the main Configuration file and store it in a Configuration object.
XMLConfigBuilder
This class is used to parse the main configuration file, which is in XML format and is parsed using dom4j+xpath
Public static Configuration loadConfiguration(InputStream config){try{// Define the Configuration object that encapsulates connection information (mybatis Configuration object) Configuration CFG = new Configuration(); //1. Obtain the object SAXReader = new SAXReader(); Document Document = reader.read(config); Element root = document.getrootelement (); List<Element> propertyElements = root.selectNodes("//property"); For (Element propertyElement: PropertyElements) {/ / judgment node is connected to the database which parts of information / / remove the name attribute value String name = propertyElement. AttributeValue (" name "); If (" driver ". The equals (name)) {/ / said drive / / get the property tag String value attribute value driver. = propertyElement attributeValue (" value "); cfg.setDriver(driver); } the if (" url ". The equals (name)) {/ / show the connection String / / get the property tag. The value of the value attribute String url = propertyElement attributeValue (" value "); cfg.setUrl(url); } if("username".equals(name)){// get the property label value of the property String username = propertyElement.attributeValue("value"); cfg.setUsername(username); } the if (" password ". The equals (name)) {/ / the password / / get the property tag the value attribute value String password = propertyElement. AttributeValue (" value "); cfg.setPassword(password); }} String packages=""; String packages=""; List<Element> list = root.selectNodes("//typeAliases/package"); for (Element xx:list){ Attribute name = xx.attribute("name"); if (name! =null){ packages = name.getValue()+"."; }} // Take all mapper tags in mappers, List<Element> mapperElements = root.selectNodes("//mappers/mapper"); For (Element mapperElement: Attribute = mapperelement. Attribute ("resource"); Attribute = mapperelement. Attribute ("resource"); if(attribute ! = null){system.out.println (" using XML"); String mapperPath = attribute.getValue(); / / get the value of the attribute "com/itheima/dao/IUserDao. XML" / / mapping configuration file content get out, Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath,packages); Cfg. setMappers(mappers); // Assign mappers in configuration to cfg.setMappers(mappers); }else{system.out.println (" use an annotation "); / / said there was no resource attribute, with annotations / / get the class attribute value String daoClassPath = mapperElement. AttributeValue (" class "); Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath); Cfg. setMappers(mappers); // Assign mappers in configuration to cfg.setMappers(mappers); }} // Return Configuration return CFG; }catch(Exception e){ throw new RuntimeException(e); }finally{ try { config.close(); }catch(Exception e){ e.printStackTrace(); }}}Copy the code
When reading a master configuration file, you can determine whether it is in the form of an annotation (class) or a configuration file (Resource) by looking at the contents of the Mapper tag.
Configuration file format
private static Map<String,Mapper> loadMapperConfiguration(String mapperPath,String packages)throws IOException { InputStream in = null; Mappers = new HashMap<String,Mapper>(); / / 1. According to the path for byte input stream in = Resources. The getResourceAsStream (mapperPath); //2. Get the Document object from the byte input stream SAXReader = new SAXReader(); Document document = reader.read(in); Element root = document.getrootelement (); //4. Obtain the namespace attribute of the root node. Value String namespace = root.attributeValue("namespace"); List<Element> selectElements = root.selectNodes("//select"); List<Element> selectElements = root.selectNodes("//select"); For (Element selectElement: SelectElements) {/ / remove the id attribute of the value of the key part of the map String id = selectElement. AttributeValue (" id "); / / remove the resultType attribute values of the map in the value of a part of the String resultType = selectElement. AttributeValue (" resultType "); if (null ! = packages && !" ".equals(packages)) { resultType=packages+resultType; String queryString = selectElement. GetText (); String Key = namespace+"."+ ID; Mapper = new Mapper(); mapper.setQueryString(queryString); mapper.setResultType(resultType); Mappers. Put (key,mapper); } return mappers; }catch(Exception e){ throw new RuntimeException(e); }finally{ in.close(); }}Copy the code
This method gets all the methods in the specified configuration file with their corresponding SQL statements and return types
Annotation form
Private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{// Defines the return value object Map<String,Mapper> mappers = new HashMap<String, Mapper>(); Class daoClass = class.forname (daoClassPath); Daoclass.getmethods () = daoclass.getMethods (); //3. Array for(Method Method: The methods) {/ / take out each method, determine whether there is a select annotations Boolean isAnnotated = method. The isAnnotationPresent (select. Class); If (isAnnotated){// Create Mapper Mapper = new Mapper(); Select selectAnno = method.getannotation (select.class); String queryString = selectAnno.value()[0]; mapper.setQueryString(queryString); / / get the current method return values, but also required with the generic Type information Type = method. The getGenericReturnType (); //List<User> // Check whether type is a ParameterizedType if(type instanceof ParameterizedType) (ParameterizedType)type; / / get the actual Type parameters of parameterized Type Type [] types = ptype. GetActualTypeArguments (); DomainClass = (Class)types[0]; String resultType = domainClass.getName(); // Assign a value to Mapper. SetResultType (resultType); String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className+"."+methodName; Mappers. Put (key,mapper); } } return mappers; }Copy the code
Gets the SQL statements and return types for all methods on the specified interface.
Write the sqlSessionFactory object
public class DefalutSqlSessionFactroy implements SqlSessionFactory { private Configuration con; public DefalutSqlSessionFactroy(Configuration con){ this.con=con; } public SqlSession openSession() { return new DefalutSqlSession(con); }}Copy the code
Write the sqlSession object
This object is the core object to achieve SQL query, in this object internal implementation of the dynamic proxy mechanism to generate proxy objects, internal encapsulation of the implementation of the logic.
public class DefalutSqlSession implements SqlSession { private Configuration con; private Connection connection; public DefalutSqlSession(Configuration con){ this.con=con; connection= JDBCUtils.getCon(con); } public <E> E getMapper(Class<E> Clazz) {return (E) proxy.newproxyInstance (clazz.getClassLoader (),new Class[]{Clazz}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Key is the method name + class name,value is the return type + SQL statement Map<String, Mapper> mappers = con.getmappers (); String name = method.getName(); String name1 = method.getDeclaringClass().getName(); String key=name1+"."+name; Mapper mapper = mappers.get(key); If (mapper==null){throw new Exception(" error "); SQL = mapper.getQueryString(); mapper.setQueryString(SqlReplace.getSql(sql,method,args)); Return new Executor().selectList(mapper,connection); }}); } public void close() { if(connection! =null){ try { connection.close(); }catch (Exception e){ e.printStackTrace(); }}}}Copy the code
The replacement of SQL
So far, the SQL is still raw, including the format of the #{} parameter substitution. We need to replace it with SQL with arguments, using the SqlReplace class.
public class SqlReplace { public static String getSql(String sql, Method Method,Object[] args){Map<String,Object> nameArgMap=getNameArgMap(Method,args); StringBuilder sqlSb=new StringBuilder(); char[] chars = sql.toCharArray(); for (int i=0; i<chars.length; i++){ char charI = chars[i]; if (charI=='#'){ if (chars[i+1]=='{'){ StringBuilder sb=new StringBuilder(); int index=i+2; while (index<chars.length){ if (chars[index]=='}'){ break; }else { sb.append(chars[index]); } index++; } if (index>= cells.length){throw new RuntimeException(" SQL "+ "index "+index); } String string = sb.toString(); Object o = nameArgMap.get(string); if (o instanceof String){ o="'"+o+"'"; } sqlSb.append(o.toString()); i=index; }else {throw new RuntimeException(" SQL format error "+ SQL +" index "+(I +1)); } }else { sqlSb.append(charI); } } System.out.println(String.format("sqlParsing:\t\t%s",sqlSb.toString())); return sqlSb.toString(); } private static Map<String, Object> getNameArgMap(Method method, Object[] args) { Map<String,Object> nameArgMap=new HashMap<>(); Parameter[] parameters = method.getParameters(); int[] index=new int[]{0}; Arrays.asList(parameters).forEach(parameter -> { nameArgMap.put(parameter.getName(),args[index[0]++]); }); return nameArgMap; }}Copy the code
The main logic for the substitution is: because the contents of #{} in the SQL match the parameter names one by one, we can obtain all the parameter names by Mehtod, and match them one by one with args, and finally replace the parameter names in the SQL with the arguments passed in.
Call JDBC to implement the query
public class Executor { public <E> List<E> selectList(Mapper mapper, Connection conn) { PreparedStatement pstm = null; ResultSet rs = null; String queryString = mapper.getQueryString(); String resultType = mapper.getresultType (); // SQL statement String resultType = mapper.getresultType (); // Return value type Class domainClass = class.forname (resultType); PreparedStatement (queryString); //2. PreparedStatement object PSTM = conn.prepareStatement(queryString); Rs = pstm.executeQuery(); List<E> List = new ArrayList<E>(); While (rs.next()) {// instantiate the entity class object to encapsulate E obj = (E) domainclass.newinstance (); ResultSetMetaData ResultSetMetaData RSMD = rs.getMetadata (); Int columnCount = rsmd.getColumnCount(); int columnCount = rsmd.getColumnCount(); For (int I = 1; i <= columnCount; ColumnName = rsmd.getColumnName(I); Object columnValue = rs.getobject (columnName); PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass); // Assign to obj: Use Java introspection mechanism (implement property encapsulation with PropertyDescriptor) PropertyDescriptor pd = New PropertyDescriptor(columnName,domainClass); Method writeMethod = pd.getwritemethod (); Invoke (obj,columnValue); // Invoke (obj,columnValue); } // Add the assigned object to the collection list.add(obj); } return list; } catch (Exception e) { e.printStackTrace(); 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(); }}}}Copy the code
The proxy object does most of its work in this method, calling JDBC and finally getting the result returned, based on SQL and the return value
test
Table structure:
The user table
Entity class:
@Data
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
Copy the code
Tests in the form of annotations
- Add in the master configuration file
- The SQL statement
- A method is called
- The execution result
Tests in the form of configuration files
- Add in the master configuration file
- The SQL statement
- A method is called
- The execution result
The annotations and configuration file form are successfully executed, and you are done
The flow chart
I drew a general flow chart to help understand
conclusion
Thank you for your reading. Recently, I also opened a personal official account: GuoCoding. I hope you can pay attention to it
At the same time, I also hope that readers can put forward any questions, we solve together! —