Mybatis has been used almost since I first learned programming. I haven’t looked at its source code. This time, I happened to study it. After reading the source code, I still got a lot of harvest. I specially sorted it out. If there are any problems, please point them out
summary
The ORM framework of mybatis actually encapsulates JDBC. Friends who have used it must know to create a mybatis_ config. XML configuration file, create a mapper interface, and create a mapper XML file, and then called in the service layer. Let’s not analyze the source code for the moment. If you develop such an ORM framework with the same functions as mybatis, there are three problems in front of you
- How to encapsulate the configuration (database link address, user name, password) and achieve registration only once, so you don’t need to take care of it in the future
- How to bind mapper interface and mapper XML file
- How to generate a proxy object, let the method in the interface find the corresponding mapper statement, and then bring the parameters in for execution
With these problems, it is better to learn the source code step by step. Of course, these problems alone cannot be fully developed. Here I will talk about them as much as possible. If there are some unpopular configurations, I may have to study them in depth by myself.
JDBC & native mybatis call review
First of all, mybatis is a layer of encapsulation of traditional JDBC. First, let’s review the traditional JDBC
JDBC
public class User {
//ID of user table
private Integer id;
//User name
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username ;
}
}
public class JDBCDemo {
//Create a connection to a database
private static Connection getConnection() {
Connection connection = null;
try {
//Load user driver
Class.forName("com.mysql.cj.jdbc.Driver");
//Address to connect to the database
String url = "jdbc:mysql://127.0.0.1:3306/test1";
//User name of the database
String user = "root";
//Password for database
String password = "12345678";
//Get a database connection
connection = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
System. out. Println (jdbcdemo. Class. Getname() + "database driver package not found!");
return null;
} catch (SQLException e) {
System. out. Println (jdbcdemo. Class. Getname() + "SQL statement has a problem, unable to query successfully!");
return null;
}
return connection;// Return to the connection
}
public User getUser(int id) {
//Get the connection to the database
Connection connection = getConnection();
//Declare a null preprocessed statement
PreparedStatement ps = null;
//Declare a result set to store the results of SQL query
ResultSet rs = null;
try {
//Preprocess and compile the SQL of the user table of the query
ps = connection.prepareStatement("select * from user where id=?");
//Set the parameter ID to the condition of the data
ps.setInt(1, id);
//Execute the query statement. Returns the result to the resultset result set
rs = ps.executeQuery();
//Traversal fetching from result set
while (rs.next()) {
//Get the user ID of the statement
int user_id = rs.getInt("id");
//Get the user name of the statement
String username = rs.getString("username");
User user = new User();
//Stored in user object
user.setId(user_id);
user.setUsername(username);
return user;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
this.close(rs, ps, connection);
}
return null;
}
/**
*Judge whether the database is closed
*@ param RS check whether the result set is closed
*@ param stmt preprocessing SQL closed
*@ param conn database connection closed
*/
private void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
System. out. Println (jdbcdemo. Class. Getname() + "resultset failed to close!");
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
System. out. Println (jdbcdemo. Class. Getname() + "statement closing failed!");
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
System. out. Println (jdbcdemo. Class. Getname() + "connection shutdown failed!");
}
}
public static void main(String[] args) {
//We query the user whose ID is 1
User user = new JDBCDemo().getUser(1);
//Print out the queried data
System.out.println(user);
}
}
Here is a brief introduction to the next three main classes. Later, we will introduce how to encapsulate them
- Drivermanager: when calling a method
getConnection
WhenDriverManager
Attempts to find the appropriate driver from the drivers loaded in initialization and explicitly loads the driver using the same class loader as the current applet or application. - Connection: connection to the database. Execute the SQL statement and return the result in the context of the connection.
- Statement: an object used to execute a static SQL statement and return the results it generates.
-
Resultset: represents the database result set
Native mybatis call
After a general understanding, we can take a look at the writing method of native mybatis
mybatis_config<?xml version="1.0" encoding="UTF-8" ?> <!-- 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. --> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- autoMappingBehavior should be set in each test case --> <environments default="development"> <environment id="development"> <!-- Configure transaction manager -- > <transactionManager type="JDBC" /> <!-- Configure data source type and database link information -- > <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/> <property name="username" value="root"/> <property name="password" value="12345678"/> </dataSource> </environment> </environments> <!-- Mapper file location configuration -- > <mappers> <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/> </mappers> </configuration>
mapper
public interface AutoConstructorMapper { PrimitiveSubject getSubject(final int id); @Select("SELECT * FROM subject") List<PrimitiveSubject> getSubjects(); @Select("SELECT * FROM subject") List<AnnotatedSubject> getAnnotatedSubjects(); @Select("SELECT * FROM subject") List<BadSubject> getBadSubjects(); @Select("SELECT * FROM extensive_subject") List<ExtensiveSubject> getExtensiveSubjects(); }
xml
<?xml version="1.0" encoding="UTF-8"?> <!-- 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. --> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper"> <select id="getSubject" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject"> SELECT * FROM subject WHERE id = #{id} </select> </mapper>
use
private static SqlSessionFactory sqlSessionFactory; @BeforeAll static void setUp() throws Exception { // create a SqlSessionFactory try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } } @Test void fullyPopulatedSubject() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class); final Object subject = mapper.getSubject(1); assertNotNull(subject); } }
Parsing mybatis_ config. xml
First, let’s come to the first question, how does mybatis parse mybatis_ config. xml
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
Mybatis core classConfiguration
Is throughXMLConfigBuilder::parse
Method is created. Mybatis puts all configuration loads at the time of initialization, and the results are basically saved inConfiguration
, it only creates one during initialization, which contains mybatis config All configurations in XML, including all parsed mappers and their mapping relationships, can be found here after loading. It is the core class in mybatis. The member variables are as follows. You can see a lot of mybatis by name_ The figure of config. (the member variables are as follows. You can feel the source code of mybatis in advance. There is no code with almost no comments. By the way, you can feel the pain of watching the source code, QAQ)
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
//Aliases for common classes
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
... Method skimming
}
Xpathparser usage
Let’s take a lookXMLConfigBuilder
Construction method of
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;// Has mybatis config. Been resolved xml
//Parse mybatis config xml
private final XPathParser parser;
private String environment;
//Create and cache reflctor objects
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
}
Its interior will encapsulate aXPathParser
Object, let’s take a look at its usage first
<employee id="${id_var}">
<blah something="that"/>
<first_name>Jim</first_name>
<last_name>Smith</last_name>
<birth_date>
<year>1970</year>
<month>6</month>
<day>15</day>
</birth_date>
<height units="ft">5.8</height>
<weight units="lbs">200</weight>
<active bot="YES" score="3.2">true</active>
</employee>
@Test
void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XPathParser parser = new XPathParser(inputStream, false, null, null);
System.out.println(parser.evalLong("/employee/birth_date/year").equals(1970L));//true
System.out.println(parser.evalNode("/employee/birth_date/year").getLongBody().equals(1970L));//true
System.out.println(parser.evalNode("/employee").evalString("@id"));//${id_var}
System.out.println(parser.evalNode("/employee/active").getDoubleAttribute("score"));//3.2 }
}
XPathParser
It encapsulates the JDK nativeDocument
,EntityResolver
,XPath
,Properties
These objects are more convenient for parsing XML files. I wrote an example above for specific usage, so that all values can be obtained.
Node resolution
After initializing xmlconfigbuilder, it will be calledparse()
Method, parsing XML, mapper parsing and mapper binding are all completed in this method. Let’s look at this method
/**
*Parse mybatis config xml
*Initialization call
* @return
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//Find configuration node resolution
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode("/configuration")
This step is to getconfiguration
Node, the next step is toparseConfiguration
Method
private void parseConfiguration(XNode root) {
try {
//Resolve each node
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//Class alias registration
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//Parsing typehandler
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
It should be familiar to see here,mybatis_config.xml
Each node placed on + element is the corresponding parsing method. Because we only configureenvironments
andmappers
Let’s look at these two methods.
Parsing environment
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//Create datasource and datasourcefactory and set corresponding property values
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
Transaction
hereTransactionFactory
Responsible for creatingTransaction
, in mybatisTransaction
There are 2 subclasses of
Transaction
Defined
public interface Transaction {
/**
*Get the corresponding database connection object
*/
Connection getConnection() throws SQLException;
/**
*Commit transaction
*/
void commit() throws SQLException;
/**
*Rollback transaction
*/
void rollback() throws SQLException;
/**
*Close database connection
*/
void close() throws SQLException;
/**
*Get transaction timeout
*/
Integer getTimeout() throws SQLException;
}
JdbcTransaction
Encapsulates the transaction isolation level, connection and data source. The method is basically to call the corresponding method of connection
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
//Database connection corresponding to transaction
protected Connection connection;
//Datasource to which the database connection belongs
protected DataSource dataSource;
//Transaction isolation level
protected TransactionIsolationLevel level;
//Auto submit
protected boolean autoCommit;
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
.. Others omitted
}
Another one isManagedTransaction
It leaves the commit and rollback to the container implementation
public class ManagedTransaction implements Transaction {
... Other strategies
//Container implementation
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
}
DataSource
Finished creatingTransactionFactory
Now it’s your turnDataSourceFactory
, there’s nothing to say about this,Configuration
The construction method of will register a bunch of class aliases and then create them through reflection. There’s nothing to say about this.
public Configuration() {
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
。。。 Other strategies
}
Let’s mainly talk about datasource, that is, data source, which is actually the encapsulation of driver. Let’s see how mybatis is encapsulated.UnpooledDataSource
The core method is as follows. Is it very friendly? In fact, it is an encapsulation of traditional JDBC. It’s not much different from the example written above. The main difference is that it can support multiple and is encapsulated.
/**
*The getconnection () method and its overloaded method are implemented
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
//Class loader to load driver class
private ClassLoader driverClassLoader;
//Related configuration of database connection driver
private Properties driverProperties;
//Cache all registered database connection drivers
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
//The name of the driver for the database connection
private String driver;
//URL of the database connection
private String url;
//User name
private String username;
//Code
private String password;
//Auto submit
private Boolean autoCommit;
//Transaction isolation level
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
static {
//Register JDBC drivers with drivermanager
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
//Create a new connection at a time
private Connection doGetConnection(Properties properties) throws SQLException {
//Initialize database driver
initializeDriver();
//Create a real database connection
Connection connection = DriverManager.getConnection(url, properties);
//Configure autocommit and isolation levels for database connections
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver() throws SQLException {
If (! Registereddrivers. Containskey (driver)) {// check whether the driver has been registered
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class. forName(driver, true, driverClassLoader);// Register driver
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Driver driverInstance = (Driver) driverType. getDeclaredConstructor(). newInstance();// Create driver object
//Register the driver. Driverproxy is an internal class defined in unpooleddatasource and a static proxy class of the driver
DriverManager.registerDriver(new DriverProxy(driverInstance));
//Add drivers to registereddrivers
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
The whole line should be clear when you see it here. Let’s take a look at the tradition
private void configureConnection(Connection conn) throws SQLException {
if (defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
//Set transaction isolation level
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
private static class DriverProxy implements Driver {
private Driver driver;
DriverProxy(Driver d) {
this.driver = d;
}
。。。 slightly
}
。。。 slightly
}
You think it’s over here, far from it. I saw one when I looked at the codePooledDataSource
As we all know, it’s a waste to create a connection every time you use it. It’s best to take it. Of course, it has a better implementation, such as Druid. I think it’s good to learn the one encapsulated by mybatis. If you are interested, you can understand it. If you are not interested, you can not see it (you can directly see the Druid source code).
Let’s have a look firstPoolState
, the author maintains it with two listsConnection
Object.
public class PoolState {
//Data source object
protected PooledDataSource dataSource;
//Idle state connection collection
protected final List<PooledConnection> idleConnections = new ArrayList<>();
//Active state connection set
protected final List<PooledConnection> activeConnections = new ArrayList<>();
protected long requestCount = 0;// Number of database connections requested
protected long accumulatedRequestTime = 0;// Cumulative time of connection
protected long accumulatedCheckoutTime = 0;// Connection cumulative checkouttime duration
protected long claimedOverdueConnectionCount = 0;// Number of timeout connections
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;// Cumulative timeout
protected long accumulatedWaitTime = 0;// Cumulative waiting time
protected long hadToWaitCount = 0;// Waiting times
protected long badConnectionCount = 0;// Invalid number of connections
}
Now let’s take a look at how mybatis is implementedPooledDataSource
, the core is to get and put backConnection
I feel that the logic of thread pool is similar.
public class PooledDataSource implements DataSource {
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);//
If (conn.isvalid()) {// is the connection valid
//Check whether the number of idle connections has reached the online limit
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state. accumulatedCheckoutTime += conn.getCheckoutTime();// Cumulative checkout duration
if (!conn.getRealConnection(). Getautocommit()) {// roll back uncommitted transactions
conn.getRealConnection().rollback();
}
//Create pooledconnection
//The proxy object is actually eliminated, and the realconnection is actually used
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state. idleConnections. add(newConn);// Add to inactive collection
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();// Set the original pooledconnection object to invalid
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();
} else {
//The free collection is full. Close it directly
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
//Spin without getting the connection object
while (conn == null) {
synchronized (state) {
/**
*Processing whether there are idle connections
*/
//Idleconnections idle state connections
If (! State. Idleconnections. Isempty()) {// idle connection
// Pool has available connection
conn = state. idleConnections. remove(0);// Get connection
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
//Pool does not have available connection if the number of active connections does not reach the maximum, a new connection can be created
if (state.activeConnections.size() < poolMaximumActiveConnections) {
//Can create new connection creates a new database connection and encapsulates it into a pooledconnection object
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
}Else {// if the number of active connections has reached the maximum, you cannot create a new connection
//Cannot create new connection gets the first active connection created
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
If (longestcheckouttime > poolmaximumcheckouttime) {// check whether the connection timed out
//Can claim excess connection statistics the information of timeout connections
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
//Move timed out connections out of the activeconnections collection
state.activeConnections.remove(oldestActiveConnection);
//If the timeout connection is not submitted, it will be rolled back automatically
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
/*
Just log a message for debug and continue to execute the following
statement like nothing happened.
Wrap the bad connection with a new PooledConnection, this will help
to not interrupt current executing thread and give current thread a
chance to join the next competition for another valid/good database
connection. At the end of this loop, bad {@link @conn} will be set as null.
*/
log.debug("Bad connection. Could not roll back");
}
}
//Create a new pooledconnection object and reuse the old collection object
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
//Multiplex timestamp
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
//Timeout pooledconnection set to invalid
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
//If there are no idle connections, no new connections can be created, and no timeout connections, you can only block the wait
try {
if (!countedWait) {
state. hadToWaitCount++;// Count waiting times
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
// ping to server and check the connection is valid or not
If (conn.isvalid()) {// check whether the connection is valid
//
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state. activeConnections. add(conn);// Statistics
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
//The current connection is invalid, continue to choose
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
。。。 slightly
}
Here is theTransactionFactory
andDataSource
Encapsulated inEnvironment
, put the environment inConfiguration
The builder mode is used. If you are interested in the builder mode, you can see my blog https://juejin.cn/post/698594…
Parse mapper
There are about four types of mapper configurations. It’s easy to understand from the source code. The contents are similar. Let’s take a look at the analysis of resource
public void parse() {
//Judge whether the mapping file has been loaded
if (!configuration.isResourceLoaded(resource)) {
//Bind SQL via XML
configurationElement(parser.evalNode("/mapper"));// Processing mapper nodes
//The parsed XML is added to loadedresources
configuration.addLoadedResource(resource);
//Scan annotation binding SQL
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//Package is processed separately
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//Resolve corresponding configuration
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
becausemybatis_config
If the path of mapper is specified, the specified resource will be loaded,mapperParser.parse();
The resolved tag will be added to the final resolved result of the select methodconfiguration::mappedStatements
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
//Process the include node first
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
//According to the usegeneratedkeys configuration in the global
//Whether it is an insert statement determines whether to use the keygenerator interface
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//Parse the original SQL statement and replace #{} with?, Put parameters
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
//Gets the value of resulttype in the select tag
String resultType = context.getStringAttribute("resultType");
//If there is no corresponding in typealiasregistry, get the corresponding class object by reflection
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//Generate mappedstatements and add them to the mappedstatements of configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
I believe you can also see the annotation version. It is in
Annotation is throughMapperRegistry::addMapper
The specific process is similar to the above process. It is also used to parse the values in the above method`
MapperBuilderAssistant
`Add to configuration
Because all the parsing results are stuffed intoConfiguration
Inside, finally, put the configuration into`
DefaultSqlSessionFactory
`
The parsing part is finished
Get sqlsession
The following is to get sqlsession. Mybatis is for you by defaultDefaultSqlSession
。
//Get database connection through data source
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
//Get the transactionfactory in the environment and create one without
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//Execute SQL statement
final Executor executor = configuration.newExecutor(tx, execType);
//Create sqlsession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
When you get the mapper, a proxy object is generated for you through dynamic proxy`
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
`The source code is as follows
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//Mapper interface proxy object
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
//Create proxy object
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Mapper method execution
When executing, we just need to see how the agent executes, that is`
MapperProxyIt encapsulates a
Plainmethodinvoker `, and the final execution is to call the invoke method of this internal class
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//
return mapperMethod.execute(sqlSession, args);
}
}
During execution, the previously parsed parameters will be used and aMapperMethod
public class MapperMethod {
//Specific implementation
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Switch (command. Gettype()) {// call different methods according to the SQL type
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//Analytical parameters
Object param = method.convertArgsToSqlCommandParam(args);
//Call sqlsession to execute
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
//Finally call this one
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//Mybatis was parsed earlier_ config. XML has been loaded into the configuration
MappedStatement ms = configuration.getMappedStatement(statement);
//
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Final callCachingExecutor
, remember the previous SQL statement encapsulated inSqlSource
What’s down therems.getBoundSql(parameterObject);
Will eventually call`
sqlSource.getBoundSql(parameterObject);Create a new
Boundsql `, now you have all the SQL parameters
//
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Next, the query operation is executed, and finally theBaseExecutor
To perform the query operation
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//Query L1 cache
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//Processing for stored procedures
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//Query complete
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//Add placeholder to cache
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//Call doquery
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//Delete placeholder
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
//Get statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
//Get connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
//Get through basestatementhandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
//Create Preparedstatement
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
//Preparedstatementhandler executes the query, which should be very friendly
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
So far, the whole query process of the native mybatis is over. Here, the value introduces the query process, and the process of addition, deletion and modification is similar. I won’t repeat it here.
Reference – mybatis technology insider