MyBatis almost in my just learning programming has been in use, has not been to see its source code, this time, just to study, the source code to see again, there is no small harvest, specially arranged under, if there is any problem, welcome to point out
An overview of the
Create a mybatis_config. XML configuration file, create a mapper interface, create a mapper. XML file, and then call the service layer. Do not analyze the source code for the moment, if you assume that you develop such an ORM framework, the function of completely drink MyBatis the same, that in front of their own problems have a total of three as follows
- How to encapsulate the configuration (database link address, user name, password) so that you only register once and don’t need to deal with this later
- How to bind mapper interface to mapper.xml file
- How do YOU generate a proxy object that allows a method in the interface to find the corresponding Mapper statement, and then take the argument in to execute it
With these problems, step by step is better to learn the source code, of course, alone with these problems is not fully developed, here I will as far as possible with about it, if some of the more unpopular configuration, may have to go to their own in-depth study.
Review of JDBC& native MyBatis calls
First, MyBatis is a wrapper around traditional JDBC, so let’s review traditional JDBC first
JDBC
public class User {
/ / the user table id
private Integer id;
/ / user name
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 == null ? null : username.trim();
}
@Override
public String toString(a) {
return "User [id=" + id + ", username="+ username ; }}Copy the code
public class JDBCDemo {
// Create a database connection
private static Connection getConnection(a) {
Connection connection = null;
try {
// Load the 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";
// Database password
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 problem, query failed!");
return null;
}
return connection;// Return the connection
}
public User getUser(int id) {
// Get a 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 queries
ResultSet rs = null;
try {
// precompile the SQL for the User table
ps = connection.prepareStatement("select * from user where id=?");
// Set the parameter Id to the data condition
ps.setInt(1, id);
// Execute the query statement. Returns the result to the ResultSet ResultSet
rs = ps.executeQuery();
// iterate over the number from the result set
while (rs.next()) {
// Retrieve the user ID of Statement
int user_id = rs.getInt("id");
// Retrieve the username of Statement
String username = rs.getString("username");
User user = new User();
// In the user object
user.setId(user_id);
user.setUsername(username);
returnuser; }}catch (SQLException e) {
e.printStackTrace();
} finally {
this.close(rs, ps, connection);
}
return null;
}
/** * Check whether the database is closed *@paramRs view result set is hysteresis closed *@paramWhether the STMT preprocessing SQL is closed *@paramConn Whether the database connection is 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 failed to close!");
}
try {
if(conn ! =null) { conn.close(); }}catch (SQLException e) {
System.out.println(JDBCDemo.class.getName() + "Connection closed failed!"); }}public static void main(String[] args) {
User id = 1
User user = new JDBCDemo().getUser(1);
// Print out the queried dataSystem.out.println(user); }}Copy the code
Here is a brief introduction to the three main classes, and how to encapsulate them later
- DriverManager: When calling a method
getConnection
When,DriverManager
An attempt is made to find the appropriate driver from the driver loaded in initialization and to explicitly load the driver using the same classloader 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 its generated results.
- ResultSet: indicates the database ResultSet
Native MyBatis call
After a general understanding we can look at the native MyBatis written mybatis_config
<! Licensed under the Apache License, Version 2.0 (the "License"); 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. -->
<! DOCTYPEconfiguration
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 the data source type and the database link status.
<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 -->
<mappers>
<mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
</mappers>
</configuration>
Copy the code
mapper
public interface AutoConstructorMapper {
PrimitiveSubject getSubject(final int id);
@Select("SELECT * FROM subject")
List<PrimitiveSubject> getSubjects(a);
@Select("SELECT * FROM subject")
List<AnnotatedSubject> getAnnotatedSubjects(a);
@Select("SELECT * FROM subject")
List<BadSubject> getBadSubjects(a);
@Select("SELECT * FROM extensive_subject")
List<ExtensiveSubject> getExtensiveSubjects(a);
}
Copy the code
xml
<! Licensed under the Apache License, Version 2.0 (the "License"); 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. -->
<! DOCTYPEmapper
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>
Copy the code
use
private static SqlSessionFactory sqlSessionFactory;
@BeforeAll
static void setUp(a) throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
sqlSessionFactory = newSqlSessionFactoryBuilder().build(reader); }}@Test
void fullyPopulatedSubject(a) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
final Object subject = mapper.getSubject(1); assertNotNull(subject); }}Copy the code
Parsing mybatis_config. XML
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.}}}Copy the code
The MyBatis core class Configuration is created using the XMLConfigBuilder:: Parse method. MyBatis loads all the Configuration files at the initialization time, and saves all the result files in Configuration. It is only created at the time of initialization, it contains all the configuration of mybatis-config. XML, also includes all the resolved Mapper, and its mapping relationship, after loading is to be able to find in this, is the core class of Mybatis. The member variables are listed below, and you can see quite a bit of mybatis_config by name. (Member variables are as follows, you can first feel the mybatis source code without almost no comments, by the way feel the pain OF my 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;
protectedClass<? > 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>
*/
protectedClass<? > configurationFactory;protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
// Alias of a common class
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 = newHashMap<>(); . Method skim}Copy the code
XPathParser usage
Let’s take a look at the constructor of XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;// Whether mybatis-config.xml has been parsed
/ / parsing mybatis - config. XML
private final XPathParser parser;
private String environment;
// Create and cache a Reflctor object
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, newXMLMapperEntityResolver()), environment, props); }}Copy the code
It wraps an XPathParser object inside, so let’s look at how it’s used
<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>
Copy the code
@Test
void constructorWithInputStreamValidationVariablesEntityResolver(a) 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}
}
Copy the code
XPathParser encapsulates JDK native Document, EntityResolver, XPath, and Properties objects, making it easier to parse XML files. I wrote an example above, so that each value can be obtained.
The node analytical
After the XMLConfigBuilder is initialized, its parse() method is called. Parsing XML, mapper parsing, and Mapper binding are all done within this method. So let’s look at this method
/** * Parses mybatis-config. XML * initializes the call *@return* /
public Configuration parse(a) {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// Find the configuration node resolution
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
Copy the code
parser.evalNode("/configuration")
This step is fetchconfiguration
Node, the next step is atparseConfiguration
Method to resolve the individual child nodes
private void parseConfiguration(XNode root) {
try {
// Parse each node
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// Class name 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); }}Copy the code
You should be familiar with mybatis_config. XML by placing each node in +Element. Since we’ve only configured environments and Mappers, let’s look at those two methods.
Resolve the 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, datasourceFactory, and set the corresponding properties
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; }}}}Copy the code
Transaction
MyBatis has two subclasses of Transaction
Transaction
Defines the
public interface Transaction {
/** * Get the corresponding database connection object */
Connection getConnection(a) throws SQLException;
/** * Commit transaction */
void commit(a) throws SQLException;
/** * rollback transaction */
void rollback(a) throws SQLException;
/** * Close the database connection */
void close(a) throws SQLException;
/** * get transaction timeout */
Integer getTimeout(a) throws SQLException;
}
Copy the code
JdbcTransaction encapsulates the transaction isolation level, connection, and data source objects, and basically calls the corresponding method Connction
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
// Transactions correspond to database connections
protected Connection connection;
// The database connection belongs to the datasource
protected DataSource dataSource;
// Transaction isolation level
protected TransactionIsolationLevel level;
// Whether to commit automatically
protected boolean autoCommit;
@Override
public void rollback(a) throws SQLException {
if(connection ! =null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); }}.. Other slightly}Copy the code
Another is ManagedTransaction, which hands commit and rollback to the container implementation
public class ManagedTransaction implements Transaction { ... @override public void commit() throws SQLException {// Does nothing} @override public void rollback() throws SQLException { // Does nothing } }Copy the code
DataSource
The Configuration constructor registers a bunch of class aliases and creates them by reflection.
public Configuration() { typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); . Other slightly}Copy the code
A DataSource is an encapsulation of a driver. Let’s take a look at how MyBatis encapsulates. UnpooledDataSource
The core method is as follows, is not very friendly, in fact, is a kind of encapsulation of traditional JDBC. This is not much different from the example written above, but the main difference is that it can support multiple, encapsulated.
/** * implements the getConnection() method and its overloaded method *@author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
// Load the classloader for the Driver class
private ClassLoader driverClassLoader;
// Database connection driver configuration
private Properties driverProperties;
// Cache all registered database connection drivers
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// The driver name of the database connection
private String driver;
// Database connection URL
private String url;
/ / user name
private String username;
/ / password
private String password;
// Whether to commit automatically
private Boolean autoCommit;
// Transaction isolation level
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
static {
// Register the JDBC driver 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 each time
private Connection doGetConnection(Properties properties) throws SQLException {
// Initialize the database driver
initializeDriver();
// Create a real database connection
Connection connection = DriverManager.getConnection(url, properties);
// Configure the autoCommit and isolation levels for database connections
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver(a) throws SQLException {
if(! registeredDrivers.containsKey(driver)) {// Check whether the driver is registeredClass<? > driverType;try {
if(driverClassLoader ! =null) {
driverType = Class.forName(driver, true, driverClassLoader);// Register the 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 the Driver object
DriverProxy is an inner class defined in UnpooledDataSource, which is the static proxy class of the Driver
DriverManager.registerDriver(new DriverProxy(driverInstance));
// Add the driver to registeredDrivers
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: "+ e); }}}Copy the code
This should make the whole line very clear. Let’s 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 levelconn.setTransactionIsolation(defaultTransactionIsolationLevel); }}private static class DriverProxy implements Driver {
private Driver driver;
DriverProxy(Driver d) {
this.driver = d; }... Omitted}... Slightly}Copy the code
You think that’s it? It’s not. PooledDataSource = ‘datasource’; PooledDataSource = ‘datasource’; PooledDataSource = ‘datasource’; If you’re interested, you can read about it. If you’re not, you can read about it.
Looking at PoolState first, the author maintains the Connection object with two lists.
Public class PoolState {// dataSource object protected PooledDataSource dataSource; // Protected final List<PooledConnection> idleConnections = new ArrayList<>(); // Protected Final List<PooledConnection> activeConnections = new ArrayList<>(); protected long requestCount = 0; Protected Long accumulatedRequestTime = 0; Protected long accumulatedCheckoutTime = 0; / / connections accumulated checkoutTime length protected long claimedOverdueConnectionCount = 0; / / the timeout connection number protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // Protected long accumulatedWaitTime = 0; // Cumulative wait time protected long hadToWaitCount = 0; // Wait times protected Long badConnectionCount = 0; // Invalid connection number}Copy the code
PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource = MyBatis PooledDataSource
public class PooledDataSource implements DataSource {
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);//
if (conn.isValid()) {// Whether the connection is valid
// Check whether the number of idle connections has reached the upper 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 a javax.sql.pooledconnection
// the realConnection is used
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);// Add to the 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 already full
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 if no connection object is retrieved
while (conn == null) {
synchronized (state) {
/** * There is no idle connection processing */
//idleConnections Idle connection
if(! state.idleConnections.isEmpty()) {// Free connection
// Pool has available connection
conn = state.idleConnections.remove(0);// Get the 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 upper limit, a new connection can be created
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new Connection creates a new database connection and encapsulates it as 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 reaches the maximum, new connections cannot be created
// 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 expected connection statistics about timeout connections
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// Move the timeout connection out of the activeConnections collection
state.activeConnections.remove(oldestActiveConnection);
// If the timeout connection is not submitted, it will be automatically rolled back
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);
// reuse the 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 and wait
try {
if(! countedWait) { state.hadToWaitCount++;// Count the wait 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 that 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 select
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.");
}
returnconn; }... Slightly}Copy the code
The TransactionFactory and DataSource are wrapped into the Environment, and the Environment is inserted into the Configuration, using the Builder mode. If you are interested in the Builder mode, check out my blog juejin.cn/post/698594…
Parsing Mapper
There are about four kinds of Mapper configuration, know this, look at the source code is very good to understand the content is much the same, let’s look at the resource parsing
public void parse(a) {
// Check whether the mapping file has been loaded
if(! configuration.isResourceLoaded(resource)) {// Bind SQL with XML
configurationElement(parser.evalNode("/mapper"));// Process the mapper node
// Add parsed XML 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 handled separately
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// Parse the 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 = newXMLMapperBuilder(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 = newXMLMapperBuilder(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.");
}
}
}
}
}
Copy the code
Mapperparser.parse (); mybatis_config specifies the path of mapper, which will load the specified resource. Will call the following method parses the select tag, add the parsed results configuration: : mappedStatements
public void parseStatementNode(a) {
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 {
// Configure according to useGeneratedKeys in global
// Whether it is an INSERT statement, which 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? The parameter
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");
// Get the resultType value in the select tag
String resultType = context.getStringAttribute("resultType");
// If there is no corresponding class object in typeAliasRegistry, reflect the corresponding class objectClass<? > 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 mappedStatements to configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code
I believe you also see notes version of it is in the annotations by MapperRegistry: : addMapper specific process and the process of similar, It also parses the values of the above methods and adds them to the Configuration through the MapperBuilderAssistant because all the parses are crammed into the Configuration. Finally, You’re done inserting configuration into the DefaultSqlSessionFactory parsing section
Get SqlSession
Mybatis will give you DefaultSqlSession by default.
// Get the database connection from the 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 without creating one
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// Execute the SQL statement
final Executor executor = configuration.newExecutor(tx, execType);
/ / create a 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(); }}Copy the code
When you get the Mapper, this is through dynamic proxy, Give you generate a proxy object 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); } 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 a 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); }Copy the code
The mapper method executes
And when we do that we just have to look at how the proxy is executed which is MapperProxy which internally encapsulates a PlainMethodInvoker, and the final execution is also the invoke method that calls this inner class, okay
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super(a);this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//
returnmapperMethod.execute(sqlSession, args); }}Copy the code
At execution time, we use the previously parsed parameters and construct a MapperMethod
public class MapperMethod {
// Specific implementation
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {// Call different methods based on 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 {
// Parse the parameters
Object param = method.convertArgsToSqlCommandParam(args);
// Call sqlSession execution
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() + ").");
}
returnresult; }}Copy the code
@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_config. XML was loaded into the configuration file
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(); }}Copy the code
CachingExecutor (parameterObject); SqlSource (parameterObject); Sqlsource.getboundsql (parameterObject) is finally called; I’m going to create a new BoundSql, and in this SQL, I’ve got all the 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);
}
Copy the code
The query operation is then performed, and eventually BaseExecutor is called 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 level-1 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 {
// The query is complete
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482clearLocalCache(); }}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 placeholders to the cache
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
/ / call the doQuery
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// Delete the 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); }}/ / for the 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 the Connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
returnconnection; }}// Get this from 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 a 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 {
returnconnection.prepareStatement(sql, keyColumnNames); }}else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
returnconnection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); }}/ / PreparedStatementHandler execute queries, this should be very kind
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
Copy the code
At this point, the whole query process of native MyBatis is over. Here, the value introduces the query process, and the process of adding, deleting and changing is almost the same. There is no further details here.
Reference — MyBatis Technology Insider