Mybatis framework Overview
Mybatis is a framework of persistence layer, which encapsulates JDBC internally, so that development only needs to focus on THE SQL statement itself, and there is no need to deal with loading drivers, creating connections, creating statements, etc. Here we also analyze it through an example.
Start by writing a SQLmapconfig.xml
<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE configuration PUBLIC"- / / mybatis.org//DTD Config / 3.0 / EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <! -- Configure Mybatis environment --> <environmentsdefault="mysql"> <! -- Configure mysql --> <environment id="mysql"> <! <transactionManager type="JDBC"></transactionManager> <! --> <dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="JDBC: mysql: / / 127.0.0.1:3306 / SSM? characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/> </dataSource> </environment> </environments> <! -- configure mybatis mapping location --> <mappers> <mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
Copy the code
Create a new mappers directory under the Resources directory, and then a new usermapper.xml configuration file
<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE mapper PUBLIC"- / / mybatis.org//DTD Mapper / 3.0 / EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jd.dao.UserMapper">
<select id="findUser" resultType="com.jd.domain.User">
select id ,userName as userName,birthday as birthday,sex as sex,
address as address FROM user where yn=1
</select>
</mapper>
Copy the code
Create a new entity class
@Data
public class User {
private Integer id;
private Date birthday;
private String userName;
private String sex;
private String address;
private Integer yn;
}
Copy the code
Create an interface class
public interface UserMapper {
/** * Query the user */
public List<User> findUser(a);
}
Copy the code
Writing test classes
public class MybatisTest {
@Test
public void test(a) throws IOException {
// The first part of the initialization work, parsing the configuration file
// read the configuration file
InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
// create a constructor object for SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// create factory object SqlSessionFactory with constructor
SqlSessionFactory factory= builder.build(in);
// Execute SQL
// create SqlSession with SqlSessionFactory
SqlSession session=factory.openSession();
// create a proxy object for the DAO interface using SqlSession
UserMapper userDao =session.getMapper(UserMapper.class);
//6. Execute query method with proxy object
List<User> list=userDao.findUser();
for (User user:list){
System.out.println(user);
}
// release resources
session.close();
try {
in.close();
} catch(IOException e) { e.printStackTrace(); }}}Copy the code
In this position, there’s actually another way we could write it,
Let’s talk about using proxy objects to call methods in the interface
Next we will analyze the principle of native Mybatis
There are two main parts,
This part of the code mainly does the initialization work and parses the configuration file
This part of the code is mainly to execute SQL
First, let’s analyze the first part
// read the configuration file
InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
// create a constructor object for SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// create factory object SqlSessionFactory with constructor
SqlSessionFactory factory= builder.build(in);
Copy the code
Start on source code:
// 1. The build we originally called
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
// Overloaded methods are called
return this.build((InputStream)inputStream, (String)null, properties);
}
// 2. Overloaded methods called
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// XMLConfigBuilder is a class that parses the configuration file of Mybatis
XMLConfigBuilder e = new XMLConfigBuilder(inputStream, environment, properties);
// call an overloaded method of buid. E.p arse() returns the Configuration object
var5 = this.build(e.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch(IOException var13) { ; }}return var5;
}
Copy the code
Solution: What is the object of Configuration
The object of Configuration is similar to that of an XML Configuration file
For example, tags in an XML configuration file include:
Properties, Settings, typeAliases, typeHandlers, objectFactory, Mappers, etc
A member variable of the Configuration class
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected boolean useActualParamName;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set<String> lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected Properties variables;
protected ReflectorFactory reflectorFactory;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protectedClass<? > configurationFactory;protected final MapperRegistry mapperRegistry;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements;
protected final Map<String, Cache> caches;
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements;
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap;
// omit some source code
}
Copy the code
The essence of initialization is to encapsulate the data in the XML Configuration file into the properties of the Configuration class.
Next, we continue to analyze the method e. press
// Go to prase in XMLConfigBuilder
public Configuration parse(a) {
if(this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
// Parser is an XPathParser object that reads data from nodes. < Configuration > is a MyBatis configuration file
// the top-level tag in
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration; }}// Configuration method (parseConfiguration). This method encapsulates the tags in the XML Configuration file in the Configuration property
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties e = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(e);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(e);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ var3, var3); }}Copy the code
At this point the parsing of the XML is complete, and then we go back to the overload method of build
Var5 = this.build(e.parse());
// Pass in the Configuration object as an entry and return a SessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Copy the code
Now comes the second part of executing the SQL
SqlSession is an interface that has a default implementation class DefaultSqlSession. A session requires only one SqlSession, which needs to be closed () upon completion.
Executor is also an interface. It has three implementation classes, BatchExecutor (reuses statements and performs batch updates), ReuseExecutor (reuses prepared statements), and SimpleExecutor (plain Executor, default).
// Create a SqlSession using SqlSessionFactory
SqlSession session=factory.openSession();
Copy the code
Get the session
// enter openSession in DefaultSqlSessionFactory
public SqlSession openSession(a) {
//getDefaultExecutorType() returns a SimpleExecutor
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null.false);
}
/ / 7, into the openSessionFromDataSource this method
/ / ExecutorType as the Executor of the type, TransactionIsolationLevel for transaction isolation level, whether the autoCommit open transactions
// Multiple overloaded openSession methods can specify the Executor type of the SeqSession obtained and the handling of the transaction
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment e = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
// Create an Executor of the specified type based on the arguments
Executor executor = this.configuration.newExecutor(tx, execType);
// Return DefaultSqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
Copy the code
Let’s go back to the previous test code
// Create a proxy object for the DAO interface using SqlSession
UserMapper userDao =session.getMapper(UserMapper.class);
// Use proxy objects to perform query methods
List<User> list=userDao.findUser();
Copy the code
The Mapper interface class does not implement this interface class, but allows you to call the methods in it. This is actually using JDK dynamic proxy technology in design mode. If you are not very clear about this dynamic proxy, you can read my blog. Blog.csdn.net/qq_30353203…
MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry: MapperRegistry Each interface corresponds to a factory. Mappers can configure the package path of the interface, or a specific interface class
When the Mappers tag is parsed, it determines that it is parsing a Mapper configuration file and then wraps the add, delete, change check tag 1 in the corresponding configuration file into an MappedStatement object, which is stored in mappedStatements.
When mappers are parsed to an interface, the MapperProxyFactory object is created and stored in the HashMap, key = the bytecode object of the interface, Value = MapperProxyFactory object corresponding to this interface.
// Enter the MapperRegistry class
public class MapperRegistry {
private final Configuration config;
// Create a HashMap where key is the bytecode object and value is the MapperProxyFactory for the corresponding interface class
private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =new HashMap();
// Determine the type and, if it is the parsed interface type, store it in the HashMap
public <T> void addMapper(Class<T> type) {
if(type.isInterface()) {
if(this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if(! loadCompleted) {this.knownMappers.remove(type); }}}} omit part of the source}Copy the code
Mybatis uses JDK dynamic proxy to create proxy objects.
// Create a proxy object for the DAO interface using SqlSession
UserMapper userDao =session.getMapper(UserMapper.class);
Copy the code
// Enter the DefaultSqlSession class, which implements the SqlSession interface
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
// Go to the getMapper method in the Configuration class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
// Enter the getMapper method of the MapperRegistry class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory = MapperProxyFactory = MapperProxyFactory = MapperProxyFactory
MapperProxyFactory mapperProxyFactory =
(MapperProxyFactory)this.knownMappers.get(type);
if(mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// Instantiate objects through dynamic proxy factories
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: "+ var5, var5); }}}// Enter the newInstance method in MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
// Create the JDK dynamic proxy Handler class
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface,
this.methodCache);
// Call the constructor newInstance
return this.newInstance(mapperProxy);
}
// This constructor, an overloaded method, is returned by a new example created by the dynamic proxy. NewProxyInstance has three incoming arguments, the last of which needs to implement the InvocationHandler interface class. The returned object is the dynamic proxy object that executes the Invoke method
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
MapperProxy implements the InvocationHandler interface class
public class MapperProxy<T> implements InvocationHandler.Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
// The proxy object in each session is different from the proxy object in each session.
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache; }}Copy the code
After dynamic proxy returns an instance, we can call the method in the Mapper interface, indicating that the method has been implemented for us in the Invoke method in MapperProxy.
// Go to the invoke method in MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if(this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args); }}catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
// This is the important point: MapperMethod finally calls the executing party
return mapperMethod.execute(this.sqlSession, args);
}
// Enter the excute method
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
// Execute different CRUDS depending on the order of command transfer, still calling SqlSession methods.
switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
case 1:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case 2:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case 3:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case 4:
if(this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if(this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if(this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if(this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case 5:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method \'" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
returnresult; }}Copy the code
At this point, the principle of Mybatis, source code analysis is over