Cover: Luo Xiaoxi
Author: Pan Pan
In the next half year, I will be honored to study and communicate with my mentors. I will take this opportunity to record what I have learned and felt.
After graduation, I started my own business first and then went to work, floating up and down for nearly 8 years. I was really anxious in my heart. Although I always took the course, I did not calm down to study, think and comb on the technical road.
I hope that the knowledge I have recorded will be of some help to the later learners.
If you have any suggestions or opinions on the content of this article, or hope to exchange and learn about Internet development, or simply love life, please feel free to wechat me: Panshenlian
The first series of articles focused on the “Architect (Java) Technical Thread” and was updated from time to time.
The first article I to “handwriting a set of persistence layer framework” to play a sample, this article we do not introduce MyBatis, will not analyze the source code, we first talk about a Java API: JDBC.
JDBC is Java’s old friend, let’s get to know him again, pick his faults, stand on the perspective of Java’s old friend, give him some optimization advice, and send him a set of “custom persistence layer framework”.
Tips:
If you are in the process of reading, there are questions about some solutions, I suggest that you first read with questions, digestion understanding, because tutors are indeed through the study of Mybatis and other persistent layer framework source code, in turn analysis.
Simply put, “Big factories write like this, and we follow it like this.”
Mybaits Series full solutions (continuously updated)
- Mybatis series full solution (a) : handwriting a set of persistent layer frame
- Mybatis series full solution (2) : Mybatis introduction and environment construction
- Mybatis series full solution (3) : Mybatis simple CRUD use introduction
- Mybatis series full solution (four) : the most complete network! Mybatis configuration file XML overview
- Mybatis series full solution (5) : the most complete network! Mybatis Mapper mapping file
- Mybatis series full solution (6) : Mybatis most core API you know how many?
- Mybatis series full solution (7) : Dao layer two ways to achieve
- Mybatis series full solution (8) : Mybatis dynamic SQL
- Mybatis series full solution (9) : Complex mapping of Mybatis
- Mybatis series full solution (10) : Mybatis annotation development
- Mybatis series full solution (11) : Mybatis cache full solution
- Mybatis plug-in development
- Mybatis series full solution (13) : Mybatis code generator
- Spring integrates Mybatis
- Mybatis series full solution (15) : SpringBoot integrated Mybatis
- Mybatis series full solution (16) : Mybatis source code analysis
Who is JDBC?
JDBC is who? Stem what? How much can you fight? See what our friends on the Internet have to say.
Java Database Connectivity (JDBC) is an application programming interface in the Java language that regulates how a client program accesses a Database, providing methods such as querying and updating data in a Database.
— From Baidu Encyclopedia
JDBC(Java DataBase Connectivity) is a Java API for executing SQL statements. It provides unified access to multiple relational databases. It consists of a set of classes and interfaces written in the Java language. JDBC provides a benchmark against which more advanced tools and interfaces can be built to enable database developers to write database applications.
— From Encyclopedia 360
. This website cannot be accessed
— From Wikipedia
So That’s basically the introduction of JDBC, the official and strict version of That’s It, and let’s move on to some of its highlights.
Since the Java language was officially announced in May 1995, Java has taken the world by storm. There are a number of programs written in the Java language, including database applications. Without a Java API, programmers had to add ODBC function calls in C to their Java programs. This prevents many of Java’s best features, such as platform independence and object-oriented features, from being fully exploited. With more and more programmers on the Java language increasingly love, more and more companies in Java program development to invest more and more energy, the Java language interface to access the database API requirements more and more intense. Also due to ODBC has its shortcomings, such as it is not easy to use, no object-oriented features and so on, SUN decided to develop a Java language interface for database application development interface. JDBC was an optional component in jdk1.x, and the SQL class package (also known as JDBCAPI) became a standard component of the Java language when JDK1.1 was released.
Later from JDBC1.0 to JDBC4.0, all the way.
— from the Internet
With the introduction to deepen our understanding of JDBC.
But I wonder how he works on a normal day? Take a look at the JDBC Basic Architecture:
With JDBC, sending SQL statements to a variety of relational databases is a breeze.
In other words, instead of having to write a program to access a Sybase database, another program to access an Oracle database, another program to access a Mysql database, and so on, a programmer can just write a program using the JDBC API. It sends SQL calls to the corresponding database.
At the same time, the combination of the Java language and JDBC allows programmers to write a program once and run it on any platform instead of writing different applications for different platforms, which is the “write once, run everywhere” advantage of the Java language.
Let’s look at the details of his work.
After all, it’s been said that the way to understand a person is to understand their work.
How does JDBC work?
The JDBC API allows applications to access tabular data in any form, especially data stored in relational databases.
The execution process is mainly divided into three steps:
- Connect to the data source.
- Pass query and update instructions to the database.
- Process the database response and return the result.
But in reality, every step of the process is extremely detailed:
Use process (details)
1. Load the database driver:
The JVM will look for and load the specified Driver Class, and execute the static code segment of the Driver Class. Prior to JDK1.6, the JDBC specification specified that each Driver Class must register an instance with DriverManager in the static code segment. Driver classes implemented after JDK1.6 no longer need to register instances actively because DriverManager already scans and initializes all jar classes that implement java.sql.Driver during the initialization phase.
- Create database connection:
DriverManager iterates through all registered drivers to try to get a connection. If the first one matches, it returns directly and uses the corresponding driver to establish a network connection between the client and the database server.
- Create a compiled object:
Once a connection is made, a statement is sent to the database. A single SQL statement is executed. A connection can be executed multiple times, unless the connection is closed. It can also be one-to-many, depending on whether you want to commit multiple request statements as the same transaction, or commit one transaction at a time. JDBC default is that transactions are committed automatically, that is, auto-commit is enabled, so the default is one-to-one.
- Set input parameter to execute SQL:
To prevent SQL injection, we use preprocessing in SQL. As placeholders for input parameters, the SQL is compiled into a secure SQL statement that can then be queried (we can talk about why preprocessing prevents SQL injection).
- Encapsulate the return result set:
Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util.Iterator: java.util. Also, the ResultSet class itself does not provide hasNext methods, so we are constantly positioning while(rs.next()) to read data through different types of accessors (such as getString,getInteger, etc.).
- Release database connection resources:
Considering that the database connection occupies the memory resources of the database server, so it is impossible to establish unlimited connection, release after use, and form a good habit. At present, many mature data connection pool technologies can optimize the data connection problem of the management.
This example uses JDBC to operate the mysql database. Let’s look at the structure of our final project and the structure of the JDBC API in JDK rt.jar:
- Project Structure:
- JDBC API rt.jar in JDK
The Java development environment and mysql database are available by default
- Create the Mave project and introduce mysql driver dependencies
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
</dependencies>
Copy the code
- Create a Java test class
package com.panshenlian.jdbc;
import com.panshenlian.po.User;
import java.sql.*;
/ * * *@Author: panshenlian
* @Description: Demonstrates connecting to the mysql database over JDBC *@Date: Create in 20:11 2020/11/10
*/
public class Test01 {
public static void main(String[] args) {
User user = new User();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// Load the database driver
Class.forName("com.mysql.jdbc.Driver");
// Get the database connection through the driver management class
connection =
DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mybatis"+
"? characterEncoding=utf-8"."root"."123456");
// Define SQL statements? Represents a placeholder
String sql = " select * from user where username = ? ";
// Get the preprocessed Statement object
preparedStatement = connection.prepareStatement(sql);
// Set parameters
// The first argument is the sequence number of the arguments in the SQL statement (starting from 1).
// The second argument is the value of the set argument
preparedStatement.setString(1."panshenlian");
// Issue SQL to the database to execute the query and query the result set
resultSet = preparedStatement.executeQuery();
// Iterate over the query result set
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("username");
/ / encapsulates the Useruser.setId(id); user.setUserName(name); System.out.println(user); }}catch (Exception e) {
e.printStackTrace();
} finally {
// Release resources
if(resultSet! =null) {try {
resultSet.close();
} catch(SQLException e) { e.printStackTrace(); }}if(preparedStatement! =null) {try {
preparedStatement.close();
} catch(SQLException e) { e.printStackTrace(); }}if(connection! =null) {try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
Copy the code
- Create a User class
package com.panshenlian.po;
/ * * *@Author: panshenlian
* @Description: user entity *@Date: Create in 20:10 2020/11/10
*/
public class User {
private Integer id;
private String userName;
public Integer getId(a) {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName(a) {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString(a) {
return "User{" +
"id=" + id +
", userName='" + userName + '\' ' +
'} '; }}Copy the code
- Create SQL statement
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`birthday` varchar(50) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1'.'senly'.'123'.'2020-11-10');
INSERT INTO `user` VALUES ('2'.'panshenlian'.'123456'.'2020-11-10');
Copy the code
- Execution result, nice, success.
User{id=2, userName='panshenlian'}
Copy the code
After watching this demo, do you see a problem? The whole process of using a JDBC operation database is tedious and awkward, as in this conversation:
The forehead (even though it’s o… JDBC is really annoying to you.
I understand that you need to establish connections to databases, execute SQL statements, process query result sets…
But, this whole process, can’t be optimized?
What are the optimizations for JDBC?
We usually lose weight and increase muscle, work to improve quality and efficiency, let’s break the code, one by one analysis:
// Load the database driver
Class.forName("com.mysql.jdbc.Driver");
// Get the database link from the driver management class
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8"."root"."123456");
Copy the code
-
Fault 1: The database configuration information is hard coded.
Use configuration files!
-
Problem 2: Frequently create or release database connections.
Use data connection pooling!
// Define SQL statements? Represents a placeholder
String sql = " select * from user where username = ? ";
// Get the preprocessed Statement object
preparedStatement = connection.prepareStatement(sql);
The first parameter is the serial number (starting from 1) of the parameter in the SQL statement. The second parameter is the value of the parameter
preparedStatement.setString(1."tom");
// Issue SQL to the database to execute the query and query the result set
resultSet = preparedStatement.executeQuery();
Copy the code
-
Problem 3: There are hard coding problems in SQL statements, setting parameters, and obtaining result set parameters.
Use configuration files!
// Iterate over the query result set
while(resultSet.next()){
int id = resultSet.getInt("id");
String userName = resultSet.getString("username");
/ / encapsulates the User
user.setId(id);
user.setUserName(userName);
System.out.println(user);
}
Copy the code
-
Problem 4: Manual encapsulation of returned result sets is cumbersome.
Use Java reflection, introspection!
In view of the deficiencies in each link of JDBC, we now sort out the corresponding optimization ideas and summarize them uniformly:
There is a problem | Optimization idea |
---|---|
The database configuration information is hard coded | Using configuration files |
Frequent database connection creation and release problems | Use data connection pooling |
There are hard coding problems in SQL statements, setting parameters and obtaining result set parameters | Using configuration files |
Manual encapsulation of the returned result set is cumbersome | Use Java reflection, introspection |
If you had to optimize, how would you design a persistence layer framework based on these optimization ideas?
Four, custom persistence layer framework: thinking analysis
JDBC is a personal battle, everything is done by yourself, inefficient and high risk, load drivers, build their own connections, their own…
The persistence layer framework is like a multi-work collaboration, clear division of labor, efficient execution, dedicated to the analysis of registration drive to establish a connection, dedicated to the management of the data connection pool, dedicated to the execution of SQL statements, dedicated to pre-processing parameters, dedicated to the assembly of result sets…
The purpose of the framework is to help us reduce the heavy development details and redundant code, so that we can focus more on business application development.Copy the code
What is the difference between using JDBC and using the persistence framework?
How comfortable is it for us users (mostly developers) to use frameworks?
Finding it so comfortable to have a durable framework, we just need to do two things:
- Configure the data source (address/data name/username/password)
- Writing SQL and parameter preparation (SQL statement/parameter type/return value type)
The framework, in addition to considering its own engineering design, also needs to take into account the actual use scenarios of the project, involving both ends of the stakeholders:
-
Usage side (actual project)
-
The persistence layer frame itself
The above two steps are sorted out through an architecture diagram “Basic Ideas of Handwriting Persistence Layer Framework” :
Core interface/class highlights:
collaboration | role | The name of the class definition |
---|---|---|
Read configuration files | Resource helper class | Resources |
Store database connection information | Database Resource classes | Configuration |
Responsible for storing SQL mapping definitions and storing result set mapping definitions | SQL and result set resource classes | MappedStatement |
Responsible for parsing configuration files and creating session factory SqlSessionFactory | Session factory builder | SqlSessionFactoryBuilder |
Create session SqlSession | The session factory | SqlSessionFactory |
Assign an Executor | The session | SqlSession |
Responsible for executing SQL (matching specified resource Mapped Statement) | actuator | Executor |
Normally, the project only corresponds to a set of database environment, generally corresponds to a SqlSessionFactory instance object, we use the singleton mode to create only one SqlSessionFactory instance.
If you need to configure more than one database environment, you need to extend it. For example, Mybatis supports switching between multiple test/production databases using environments.
After sorting out the basic idea of the persistence layer framework and clarifying the division of labor of each role of the framework, we began to sort out the detailed scheme:
A. The project user side calls the framework API. In addition to introducing the JAR package of the persistence layer framework, two additional configuration information should be provided:
Sqlmapconfig. XML: database configuration information (address/data name/user name/password) and the full path of mapper. XML. Mapper. XML: stores INFORMATION about SQL statements, parameter types, and return value types.Copy the code
B. The framework itself is essentially the encapsulation of JDBC code in 6 basic steps:
- Loading a configuration file: Loads the configuration file as a byte input stream and stores it in memory based on the configuration file path.
Create a Resource class that provides a method to load a stream: InputStream getResourceAsStream(String path)Copy the code
- Create two Javabeans (container objects) that hold the content parsed out of the configuration file
Configuration (core Configuration class) : Stores the contents parsed from SQLMapconfig. XML. MappedStatement (Mapping configuration class) : Stores the contents parsed by mapper. XML.Copy the code
- Parse the configuration file (using DOM4J) and create the SqlSession session object
Create class SqlSessionFactoryBuilder Build (InputStream in) > use DOM4J to parse the configuration file, encapsulate the parsed content in the container object > Create SqlSessionFactory object, produce sqlSession session object (factory mode)Copy the code
- Create the SqlSessionFactory interface and implement class DefaultSqlSessionFactory
Create openSession() interface method to produce sqlSessionCopy the code
- Create SqlSession interface and implement DefaultSqlSession class
Define CRUD operations on the database: > selectList(); > selectOne(); > update(); > delete();Copy the code
- Create the Executor interface and implement the SimpleExecutor class
Create query(Configuration conf,MappedStatement MS,Object... Params actually executes the JDBC code.Copy the code
The basic process has been clear, so let’s refine the class diagram to better facilitate our actual coding:
Simple version of
A detailed version
Final handwritten persistent layer frame structure reference:
Packet interface class description
- The config package
Interface/class | role |
---|---|
BoundSql | Replace Sql #{} with? Number and stores the parameter name corresponding to #{} |
XMLConfigBuilder | Sqlmapconfig. XML configuration file parsing utility class |
XMLMapperBuilder | Mapper. XML configuration file parsing utility class |
- iopackage
Interface/class | role |
---|---|
Resource | Read sqlMapconfig. XML and mapper. XML utility classes and convert them into input streams inputStream |
- pojopackage
Interface/class | role |
---|---|
Configuration | Encapsulates the SQLmapconfig.xml configuration parameter |
MappedStatement | Encapsulate the SQL parameters of the mapper.xml configuration |
- sqlSessionpackage
Interface/class | role |
---|---|
SqlSessionFactoryBuilder | SqlSessionFactory builder class |
SqlSessionFactory | The factory interface that produces SQLSessions |
DefaultSqlSessionFactory | The default implementation class for SqlSessionFactory |
SqlSession | The SqlSession interface defines the basic CRUD methods of the database |
DefaultSqlSession | SqlSession implementation class |
Executor | Executor interface the real Executor of SQL, using JDBC to manipulate databases |
SimpleExecutor | Executor implementation class |
- utils
Interface/class | role |
---|---|
ParameterMapping | #{}, ${}, ${}, ${ |
TokenHandler | From Mybatis framework, tag processor interface |
ParameterMappingTokenHandler | #{}, ${} become? |
GenericTokenParser | From Mybatis framework, general purpose tag parser, tag #{and} start and end processing |
Custom persistence layer framework: coding
Combining UML diagrams with project structure diagrams, something is starting to come to mind, and the tedious coding process, let’s get started.
The framework relies on POM.xml
<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.panshenlian</groupId>
<artifactId>MyPersistence</artifactId>
<version>1.0 the SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<! Dependencies required by the persistence layer framework -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</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>
Copy the code
BoundSql class under config package
package com.panshenlian.config;
import com.panshenlian.utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: SQL wildcard *@Date: Create in 16:12 2020/11/12
*/
public class BoundSql {
/** * Parsed SQL statement */
private String sqlText;
private List<ParameterMapping> parameterMappingList =
new ArrayList<ParameterMapping>();
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText(a) {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
public List<ParameterMapping> getParameterMappingList(a) {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList; }}Copy the code
XMLConfigBuilder class in config package
package com.panshenlian.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.Configuration;
import com.sun.javafx.scene.control.skin.EmbeddedTextContextMenuContent;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/ * * *@Author: panshenlian
* @Description: database configuration information parsing class *@Date: Create in 13:56 2020/11/12
*/
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder(a) {
this.configuration = new Configuration();
}
public Configuration parseConfig(InputStream inputStream) throws Exception {
Document document = new SAXReader().read(inputStream);
Element configurationRootElement = document.getRootElement();
// parses the dataSource config dataSource parameter information
List<Element> elementList = configurationRootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : elementList){
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(name,value);
}
// Use c3P0 data source
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(properties.getProperty("driverClass"));
dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
dataSource.setUser(properties.getProperty("userName"));
dataSource.setPassword(properties.getProperty("password"));
// Set the data source
configuration.setDataSource(dataSource);
// Parse mapper. XML, read the byte input stream according to the path, parse using dom4j
List<Element> mapperElementList = configurationRootElement.selectNodes("//mapper");
for (Element element : mapperElementList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parseMapper(resourceAsStream);
}
returnconfiguration; }}Copy the code
Config package XMLMapperBuilder class
package com.panshenlian.config;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: SQL configuration information parsing class *@Date: Create in 14:28 2020/11/12
*/
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public void parseMapper(InputStream inputStream) throws DocumentException {
Document mapperDocument = new SAXReader().read(inputStream);
Element rootElement = mapperDocument.getRootElement();
String namespace = rootElement.attributeValue("namespace");
// Parse each select node
List<Element> selectNodes = mapperDocument.selectNodes("//select");
for (Element element : selectNodes) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
// Parse the wrapper into the MapperdStatement object
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParameterType(parameterType);
mappedStatement.setSql(sql);
String statementId = namespace + "."+ id; configuration.getMappedStatementMap().put(statementId,mappedStatement); }}}Copy the code
IO package Resource utility class
package com.panshenlian.io;
import java.io.InputStream;
/ * * *@Author: panshenlian
* @Description: Resource *@Date: Create in 9:22 2020/11/12
*/
public class Resource {
/** * Load the configuration file into a byte input stream and store it in memory * according to the configuration file path@param path
* @return* /
public static InputStream getResourceAsStream(String path){
InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(path);
returninputStream; }}Copy the code
Pojo Configuration under the package
package com.panshenlian.pojo;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/ * * *@Author: panshenlian
* @Description: Database configuration *@Date: Create in 13:58 2020/11/12
*/
public class Configuration {
private DataSource dataSource;
/**
* key:statementId
* value:封装好的mappedStatement对象
*/
private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
public DataSource getDataSource(a) {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, MappedStatement> getMappedStatementMap(a) {
return mappedStatementMap;
}
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap; }}Copy the code
Pojo MappedStatement under the package
package com.panshenlian.pojo;
/ * * *@Author: panshenlian
* @Description: SQL and result set resource classes (responsible for storing SQL mapping definitions, storing result set mapping definitions) *@Date: Create in 14:17 2020/11/12
*/
public class MappedStatement {
/** * ID */
private String id;
/** * Return value type */
private String resultType;
/** * Parameter value type */
private String parameterType;
/** * SQL statement */
private String sql;
public String getId(a) {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType(a) {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getParameterType(a) {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getSql(a) {
return sql;
}
public void setSql(String sql) {
this.sql = sql; }}Copy the code
SqlSession DefaultSqlSession under the package
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: SQL session implementation class *@Date: Create in 14:43 2020/11/12
*/
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
// Build SQL executable
SimpleExecutor simpleExecutor = new SimpleExecutor();
// get the final execution SQL object
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
// 3, execute SQL, return result set
List<Object> queryResultList = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>)queryResultList;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (null! = objects && objects.size() ==1) {return (T)objects.get(0);
} else {
throw new RuntimeException("Query result is null or more than 1 result is returned"); }}@Override
public int update(String statementId, Object... params) {
return 0;
}
@Override
public int delete(String statementId, Object... params) {
return 0;
}
@Override
public <T> T getMapper(Class
mapperClass) {
// Use the JDK dynamic proxy to generate proxy objects for the Dao interface and return the call results
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// The bottom layer is still executing JDBC
Call selectList or selectOne, depending on the case
Parameter statementId = Unique identifier of the SQL statement: namespace.id = Fully qualified name of the interface. The method name
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
// 2. Prepare the parameter params (args)
// Gets the return value type of the called method
Type genericReturnType = method.getGenericReturnType();
// Determine whether generic type parameterization is performed
if ( genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
returnselectOne(statementId,args); }});return(T)proxyInstance; }}Copy the code
SqlSession DefaultSqlSessionFactory under the package
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
/ * * *@Author: panshenlian
* @Description: Default SqlSession factory implementation class *@Date: Create in 14:41 2020/11/12
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession(a) {
return newDefaultSqlSession(configuration); }}Copy the code
SqlSession package under the Executor
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: SQL executor interface *@Date: Create in 15:02 2020/11/12
*/
public interface Executor {
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
Copy the code
SqlSession SimpleExecutor under the package
package com.panshenlian.sqlSession;
import com.mysql.jdbc.StringUtils;
import com.panshenlian.config.BoundSql;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import com.panshenlian.utils.GenericTokenParser;
import com.panshenlian.utils.ParameterMapping;
import com.panshenlian.utils.ParameterMappingTokenHandler;
import java.beans.ExceptionListener;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: SQL executor interface simple implementation class *@Date: Create in 15:55 2020/11/12
*/
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. Register the driver and obtain the database connection
Connection connection = configuration.getDataSource().getConnection();
Select * from user where id = #{id}
Select * from user where id =?
// The conversion process also needs to parse the values in #{}
String sql = mappedStatement.getSql();
BoundSql bounSql = getBoundSql(sql);
// Get the prepared object preparedStatement
PreparedStatement preparedStatement =
connection.prepareStatement(bounSql.getSqlText());
// 4. Set parametersString parameterType = mappedStatement.getParameterType(); Class<? > parameterTypeClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = bounSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String filedName = parameterMapping.getContent();
/ / reflection
Field declaredField = parameterTypeClass.getDeclaredField(filedName);
// Violent access
declaredField.setAccessible(true);
Object declaredFieldValue = declaredField.get(params[0]); Params [0] is the object
preparedStatement.setObject(i+1,declaredFieldValue);
}
// execute SQLResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<? > resultTypeClass = getClassType(resultType); List<Object> objects =new ArrayList<Object>();
// encapsulate the return result set
while (resultSet.next()){
Object o = resultTypeClass.newInstance();
/ / metadata
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
/ / the field name
String columnName = metaData.getColumnName(i);
/ / field values
Object columnValue = resultSet.getObject(columnName);
// Use introspection (reflection) to complete encapsulation according to the correspondence between database tables and entities
PropertyDescriptor propertyDescriptor =
new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,columnValue);
}
objects.add(o);
}
return (List<E>)objects;
}
/** * Get the class * based on the full-path reflection of the argument@param parameterType
* @return* /
privateClass<? > getClassType(String parameterType)throws ClassNotFoundException {
if (StringUtils.isNullOrEmpty(parameterType)) {
return null; } Class<? > clazz = Class.forName(parameterType);return clazz;
}
/** * select * from '#{}'; 2. Parse the value inside #{} and store *@param sql
* @return* /
private BoundSql getBoundSql(String sql) {
// The tag handler class configures the tag parser to handle placeholders
ParameterMappingTokenHandler parameterMappingTokenHandler
= new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser =
new GenericTokenParser("# {"."}",
parameterMappingTokenHandler);
// Parse the SQL
String parseSql = genericTokenParser.parse(sql);
// The parsed parameter name
List<ParameterMapping> parameterMappings =
parameterMappingTokenHandler.getParameterMappings();
// Encapsulate the result into wildcard SQL
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
returnboundSql; }}Copy the code
SqlSession sqlSession under the package
package com.panshenlian.sqlSession;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: Sql session interface *@Date: Create in 14:40 2020/11/12
*/
public interface SqlSession {
/** * query all *@param statementId
* @param params
* @param <E>
* @return* /
public <E> List<E> selectList(String statementId , Object ... params) throws Exception;
/** * query a single * based on the condition@param statementId
* @param params
* @param <T>
* @return* /
public <T> T selectOne(String statementId , Object ... params) throws Exception;
/** * update * according to conditions@param statementId
* @param params
* @return* /
public int update(String statementId , Object ... params);
/** * delete * according to condition@param statementId
* @param params
* @return* /
public int delete(String statementId , Object ... params);
/** * Generates the proxy implementation class * for the Dao interface@param mapperClass
* @param <T>
* @return* /
public <T> T getMapper(Class
mapperClass);
}
Copy the code
SqlSession SqlSessionFactory under the package
package com.panshenlian.sqlSession;
/ * * *@Author: panshenlian
* @Description: SqlSession factory interface *@Date: Create in 13:51 2020/11/12
*/
public interface SqlSessionFactory {
public SqlSession openSession(a);
}
Copy the code
The SqlSessionFactoryBuilder is under the sqlSession package
package com.panshenlian.sqlSession;
import com.panshenlian.config.XMLConfigBuilder;
import com.panshenlian.pojo.Configuration;
import java.io.InputStream;
/ * * *@Author: panshenlian
* @Description: SqlSession Session factory build class *@Date: Create in 13:48 2020/11/12
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws Exception {
// Step 1: Use dom4J to parse the Configuration file and encapsulate the parsed content in Configuration
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
SqlSessionFactory = SqlSessionFactory = SqlSessionFactory
DefaultSqlSessionFactory defaultSqlSessionFactory =
new DefaultSqlSessionFactory(configuration);
returndefaultSqlSessionFactory; }}Copy the code
GenericTokenParser utils package
/** * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package com.panshenlian.utils;
/** * generic tag parser, tags #{and} to start and end processing *@author Clinton Begin
*/
public class GenericTokenParser {
private final String openToken; // Start the tag
private final String closeToken; // End tag
private final TokenHandler handler; // Mark the handler
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/** * parse ${} and #{} *@param text
* @return* This method mainly realizes the configuration file, script and other fragments of placeholder parsing, processing work, and return the final data needed. * Where parsing is done by this method and processing is done by the handler's handleToken() method */
public String parse(String text) {
// Validate argument problem, if null, return empty string.
if (text == null || text.isEmpty()) {
return "";
}
// If the start tag is not included, the default is not a placeholder. If the start tag is not included, the default is not placeholder.
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// Convert text to character array SRC, and define the default offset=0, store the variable builder that eventually needs to return a string,
// text specifies the variable name corresponding to the placeholder in the variable expression. Determine if start is greater than -1(that is, if openToken exists in text) and if so, execute the following code
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
If there is an escape character before the start token, it will not be processed as openToken, otherwise it will continue to be processed
if (start > 0 && src[start - 1] = ='\ \') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// Reset the expression variable to avoid null Pointers or old data interference.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {//// if an end flag exists
if (end > offset && src[end - 1] = ='\ \') {// If the end tag is preceded by an escape character
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {// There are no escape characters that need to be processed as arguments
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break; }}if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// The parameter is processed according to the key (expression) of the parameter. As a placeholder
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
returnbuilder.toString(); }}Copy the code
ParameterMapping utils package
package com.panshenlian.utils;
/ * * *@Author: panshenlian
* @DescriptionParameter mapping class (SQL parameter mapping class, store parameter names in #{}, ${}) *@Date: Create in 16:14 2020/11/12
*/
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent(a) {
return content;
}
public void setContent(String content) {
this.content = content; }}Copy the code
ParameterMappingTokenHandler utils package
package com.panshenlian.utils;
import java.util.ArrayList;
import java.util.List;
/** * tags the handler implementation class, parsing #{}, ${} into? * /
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context is the parameter name #{id} #{username}
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings(a) {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings; }}Copy the code
TokenHandler utils package
/** * Copyright 2009-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package com.panshenlian.utils;
/** * marks the processor interface *@author Clinton Begin
*/
public interface TokenHandler {
String handleToken(String content);
}
Copy the code
Once the framework is written, we write a test project to verify the framework. We add a new test project (in the form of a module) under the existing framework to ensure that the test project and the framework project are under the same working group:
Since I have already written the test project, I can directly introduce it, the effect is the same, create and import in module mode can be:
The basic process of test engineering is also explained:
1. Introduce dependency pom.xml
<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.panshenlian</groupId>
<artifactId>MyPersistenceTest</artifactId>
<version>1.0 the SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<! -- Introducing custom persistence layer framework dependencies -->
<dependencies>
<dependency>
<groupId>com.panshenlian</groupId>
<artifactId>MyPersistence</artifactId>
<version>1.0 the SNAPSHOT</version>
</dependency>
</dependencies>
</project>
Copy the code
2. Configure the data source SQLmapconfig.xml
<configuration>
<! -- Database configuration information -->
<dataSource>
<property name="driverClass"
value="com.mysql.jdbc.Driver" ></property>
<property name="jdbcUrl"
value="jdbc:mysql:///mybatis" ></property>
<property name="userName"
value="root" ></property>
<property name="password"
value="123456" ></property>
</dataSource>
<! -- Mapper.xml full path applied -->
<mapper resource="userMapper.xml"></mapper>
<mapper resource="orderMapper.xml"></mapper>
</configuration>
Copy the code
3, We take the user table as an example to establish the user SQL configuration usermapper.xml
<mapper namespace="com.panshenlian.dao.IUserDao">
<! StatementId --> statementId --> statementId
<select id="findAll"
resultType="com.panshenlian.pojo.User">
select * from user
</select>
<! -- User user = new User(); user.setId(1); user.setUsername("panshenlian"); -->
<select id="findByCondition"
resultType="com.panshenlian.pojo.User"
parameterType="com.panshenlian.pojo.User">
select * from user
where id= #{id} and username = #{username}
and password= #{password} and birthday = #{birthday}
</select>
</mapper>
Copy the code
4. User DAO interface
package com.panshenlian.dao;
import com.panshenlian.pojo.User;
import java.util.List;
/ * * *@Author: panshenlian
* @Description:
* @Date: Create in 21:35 2020/11/12
*/
public interface IUserDao {
/** * Query all users *@return
* @throws Exception
*/
public List<User> findAll(a) throws Exception;
/** * Perform user queries based on conditions *@return
* @throws Exception
*/
public User findByCondition(User user) throws Exception;
}
Copy the code
5. Entity class of the user DAO
package com.panshenlian.pojo;
/ * * *@Author: panshenlian
* @Description: user entity *@Date: Create in 9:20 2020/11/12
*/
public class User {
private Integer id;
private String username;
private String password;
private String birthday;
public String getPassword(a) {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday(a) {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public Integer getId(a) {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername(a) {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString(a) {
return "User{" +
"id=" + id +
", username='" + username + '\' ' +
", password='" + password + '\' ' +
", birthday='" + birthday + '\' ' +
'} '; }}Copy the code
Note: The namespace in the user SQL configuration file usermapper.xml needs to be the same as the fully qualified name of the user DAO, which is our framework default rule: Namespace = “com. Panshenlian. Dao. IUserDao” at the same time, the select tag id and user dao interface is consistent with the name of the method, the framework is also the default rules, such as id = “the.findall”
6. Eventually we created the test class MyPersistenceTest
package com.panshenlian.test;
import com.panshenlian.dao.IUserDao;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.User;
import com.panshenlian.sqlSession.SqlSession;
import com.panshenlian.sqlSession.SqlSessionFactory;
import com.panshenlian.sqlSession.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/ * * *@Author: panshenlian
* @Description: Persistence layer framework test class *@Date: Create in 9:24 2020/11/12
*/
public class MyPersistenceTest {
@Test
public void test(a) throws Exception {
InputStream resourceAsStream =
Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// Traditional DAO calls
User user = new User();
user.setId(3);
user.setUsername("panshenlian");
user.setBirthday("2020-11-12");
user.setPassword("123456");
User dbUser = sqlSession.selectOne("com.panshenlian.dao.IUserDao.findByCondition",user);
System.out.println(dbUser);
List<User> userList = sqlSession.selectList("com.panshenlian.dao.IUserDao.findAll", user);
for (User db : userList) {
System.out.println(db);
}
// Proxy mode call
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> users = userDao.findAll();
for (User db : users) {
System.out.println("Proxy call ="+ db); }}}Copy the code
7. Run the test class, and the result meets the expectation
We have basically completed the framework and test verification. In fact, the above is mainly a simple framework introduction for the persistence layer framework. In terms of Mybatis framework analysis, we have basically achieved a simulation prototype, and the process is roughly like this.
The implementation of coding involves several interesting points that we will talk about later, including:
- Introspection mechanism
- Reflection mechanism
- JDK dynamic proxy
- Design patterns
- The generic
conclusion
Now large projects typically do not use JDBC directly, or using mature persistence layer solution on the market, either from the persistence layer framework, at the end of the day, or a simple JDBC can not ensure the stability of the high efficient data access and application layer, and the persistence layer framework scheme is growing, not only eliminate a lot of JDBC redundant code, It also provides a very low learning curve, which not only ensures collaboration with traditional databases but also accepts SQL statements, but also provides extended integration support for other frameworks, including connection pool, cache, performance and so on, which has been greatly optimized and improved, so the popularity of the framework is an inevitable trend.
JDBC was also brilliant and great when it was born in the 1990s, but as the technology level jumped and the business scene was updated iteratively, the old technology could not meet the existing demands, everything would be updated in rotation, and we just stood on the shoulders of the great people, along with the change.
Well, that’s all. Good night.
In the next post, we may talk about Mybatis infrastructure and architecture.
BIU ~ the article continues to update, wechat search “Pan Pan and his friends” the first time to read, there are surprises at any time. This article will be included on GitHub github.com/JavaWorld, hot technology, framework, surface, solution, we will be the most beautiful posture in the first time, welcome Star.