前言
最近看了一個講如何面試的視訊,裡面說到不要隻寫自己閱讀過某某源碼,要把它展現在項目中,就算自己做的項目中沒有,也可以說自己看到别人的項目中利用了某個架構的某些特性,于是我就準備自己動手試試,學習一下優秀架構的精髓,我手動整合了spring和mybatis,視圖體會mybatis的優秀之處。
開始
要開始整合spring和mybatis,自然是要先搭建一個maven的項目,我在整合spring和mybatis的同時還整合了log4j友善檢視日志,整合了阿裡的druid作為mysql的資料庫連接配接池,由于我隻是要整合spring和mybatis并找到mybatis基于spring的擴充點及整合的原理,沒有必要使用web項目,是以我隻引入了spring-context包。項目pom檔案如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ww</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.25.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.25.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
</project>
由于我不喜歡使用xml,是以本次搭建我是用了純注解的形式,在項目的resource目錄下,我建立了application.properties檔案
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/testmybatis
spring.datasource.driver=com.mysql.jdbc.Driver
編寫配置類,擷取application.properties中配置的資料庫資訊
@Configuration
@PropertySource("classpath:application.properties")
public class PropertiesConfig {
@Value("${spring.datasource.url}")
public String url;
@Value("${spring.datasource.username}")
public String username;
@Value("${spring.datasource.password}")
public String password;
@Value("${spring.datasource.driver}")
public String driver;
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getDriver() {
return driver;
}
}
mapper檔案如下
public interface UserMapper {
@Select("select id,name,height,weight from user where id=#{id}")
public User selectUser(Integer id);
}
實體類
@Data
public class User {
private int id;
private String name;
private String height;
private String weight;
}
相對應的,在mysql中我建了一個資料庫,表名為user
service類
@Service
public class UserService{
@Autowired
UserMapper mapper;
public User getUser(int id) {
//一開始log4j并沒有輸出日志,在官網上查了之後說加上這句話就可以列印日志了
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
return mapper.selectUser(id);
}
}
讓spring來啟動的主配置類
@Configuration
@ComponentScan("com.ww")
@MapperScan("com.ww.mapper")
@PropertySource("classpath:application.properties")
public class MybatisConfig {
//這些都是mybatis-spring官網上的例子,照着改改就行
@Bean
public DataSource dataSource(PropertiesConfig config) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(config.getDriver());
dataSource.setUrl(config.getUrl());
dataSource.setPassword(config.getPassword());
dataSource.setUsername(config.getUsername());
return dataSource;
}
//這些都是mybatis-spring官網上的例子,照着改改就行
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
}
到這裡項目算搭建完成了,接下來我們運作項目,來觀察mybatis整合spring之後的運作過程,以及mybatis整合spring和不整合spring究竟有什麼不同
首先我們看到主配置類上有一行@MapperScan的注解,表示掃描mapper到spring容器中,把mapper交給spring管理,那麼我想知道mapper是什麼時候被spring掃描并注入的呢,我們點進這個注解,看到這個注解是一個組合注解,其中有這麼一行注解引起了我的注意
@Import(MapperScannerRegistrar.class)
這個注解是什麼意思呢,Import注解的意思是導入資源,那麼我們看看它導入的是個什麼資源,看類的名字,我猜想這可能是一個mapper掃描器的注冊器,于是我就點進去看看
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
//調用ClassPathMapperScanner中的doScan方法,來掃描mapper并組裝成beanDefinition
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
可以看到這個類實作了ImportBeanDefinitionRegistrar這個接口,這就引出了spring的第一個擴充點,ImportBeanDefinitionRegistrar接口可以用來動态注冊bean,它可以支援我們自己寫的代碼封裝成BeanDefinition對象。在這裡,mybatis作為一個第三方架構因為沒有辦法像第一方元件那樣使用@Component或者@Service來表示這是一個需要注入的bean,是以隻能擴充這個接口來動态的注入bean。
我們再來看registerBeanDefinitions這個方法,這個方法的含義就是注冊bd(為了友善起見,以下beanDefinition都簡稱bd),我們看到方法最後調用了ClassPathMapperScanner的doScan()方法,我們看看ClassPathMapperScanner這個類
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
...
}
這個類屬于spring-mybatis包下,繼承了spring的ClassPathBeanDefinitionScanner類
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//調用spring的doScan方法來掃描bd
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//掃描完成後執行bd處理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
由于spring先執行componentScan,我把斷點打在ClassPathBeanDefinitionScanner的doScan方法中的時候,首先掃描我自己的bean的時候也會進入這個斷點,本次由于不分析componentScan,這一部分略過。跳過這個斷點,此時控制台上輸出
Registering bean definition for @Bean method com.ww.config.MybatisConfig.dataSource()
Registering bean definition for @Bean method com.ww.config.MybatisConfig.sqlSessionFactory()
表示我自己的bean已經注冊完成了,接下來就應該進入mapper掃描了,我把斷點打在MapperScannerRegistrar類中registerBeanDefinitions()方法的第一行,果然斷點跳了進來,一路執行下去,當執行完ClassPathBeanDefinitionScanner的doScan的方法之後,控制台輸出一句:
Identified candidate component class: file [E:\mycode\gitclone\mybatis-spring\target\classes\com\ww\mapper\UserMapper.class]
确認了候選的元件類,也就是說明spring已經掃描到mapper了,同時spring已經注冊了bd,接下來再執行,則會進入processBeanDefinitions()方法
//處理注冊好的bd
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//bd中加入mapperFactoryBean類,由此可見一個mapper對應的beanClass就是mapperFactoryBean,這是mybatis核心類之一,會詳細說
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
//自動注入類型為byType
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
等這個方法處理完成之後,整個bd算是建立完了,接下來mybatis就開始初始化的過程了,首先我們來看之前被添加進bd中的MapperFactoryBean類
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
//------------- mutators --------------
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means
* it must have been included in mybatis-config.xml.
* <p/>
* If it is true, the mapper will be added to MyBatis in the case it is not already
* registered.
* <p/>
* By default addToCofig is true.
*
* @param addToConfig
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already
* registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}
MapperFactoryBean擴充了Spring的FactoryBean接口,FactoryBean作為Spring的擴充點,FactoryBean的功能是可以讓我們自定義Bean的建立過程
//傳回的對象執行個體
T getObject() throws Exception;
//Bean的類型
Class<?> getObjectType();
//true是單例,false是非單例 在Spring5.0中此方法利用了JDK1.8的新特性變成了default方法,傳回true
boolean isSingleton();
同時MapperFactory還繼承了SqlSessionDaoSupport類,SqlSessionDaoSupport類又繼承了Spring的DaoSupport類,利用了Spring中InitializingBean這個擴充點,在屬性設定之後對bean進行操作,我們來看SqlSessionDaoSupport類和DaoSupport類都是做什麼的
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
//設定sqlSessionFactory,由于mapperScan最後執行了definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE),是以這個set方法會自動注入進spring容器中
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
//設定sqlSessionTemplate,spring-mybatis的核心類之一,替代了普通mybatis中的DefaultSqlSession類,這個類控制sqlSession,包含一個内部類用來執行動态代理,同時sqlSessionTemplate類線程安全,可以在spring中作為單例bean使用
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
/**
* Users should use this method to get a SqlSession to call its statement methods
* This is SqlSession is managed by spring. Users should not commit/rollback/close it
* because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSession;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
//實作InitializingBean接口,會執行afterPropertiesSet()方法,在mybatis整合了spring後會執行MapperFactoryBean類中的checkDaoConfig()方法
public abstract class DaoSupport implements InitializingBean {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
/**
* Abstract subclasses must override this to check their configuration.
* <p>Implementors should be marked as {@code final} if concrete subclasses
* are not supposed to override this template method themselves.
* @throws IllegalArgumentException in case of illegal configuration
*/
protected abstract void checkDaoConfig() throws IllegalArgumentException;
/**
* Concrete subclasses can override this for custom initialization behavior.
* Gets called after population of this instance's bean properties.
* @throws Exception if DAO initialization fails
* (will be rethrown as a BeanInitializationException)
* @see org.springframework.beans.factory.BeanInitializationException
*/
protected void initDao() throws Exception {
}
}
把斷點打在MapperFactoryBean類中的checkDaoConfig()方法上,繼續執行,我們看到執行了configuration.addMapper(this.mapperInterface),點進去,我們看到跳入了org.apache.ibatis.session.Configuration類中的addMapper方法
//mapper注冊器,添加和擷取mapper的實際類
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
于是乎再進入一層
//mapper被加入到MapperProxyFactory類中
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
//加入到map中後立即解析mapper中的注解,目的是拿到mapper中的注解sql語句
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
//動态代理回調方法
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//mapper代理類的執行個體化方法
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
繼續往下執行,同時注意控制台的輸出情況,看到控制台輸出一句Finished creating instance of bean 'userMapper',此時mapper執行個體化完成,既然執行個體化完成了,那麼就要傳回執行個體化好的mapper,由于bd中放的beanClass是mapperFactoryBean,是以mapper執行個體要從mapperFactoryBean的getObject方法中來獲得,來看代碼
//org.mybatis.spring.mapper.MapperFactoryBean#getObject
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
//org.mybatis.spring.SqlSessionTemplate#getMapper
//sqlSessionTemplate作為sqlSession
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
//org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//org.apache.ibatis.binding.MapperRegistry#getMapper
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 {
//之前說過,這裡傳回的是個代理的對象,也就是經過動态代理的mapper
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
我們觀察變量的變化,發現最終傳回的object被MapperProxy所代理,到此為止,mapper已經被spring所管理,spring将通過動态代理技術使用代理類來執行mapper中的各種操作。
總結
經過自己動手用純注解的方式整合spring和mybatis,我們看到了mybatis基于spring做的許多擴充,同時也看到了spring的很多擴充點,比如ImportBeanDefinitionRegistrar、InitializingBean、FactoryBean,下面總結一下mybatis整合spring過程中所用到的幾個關鍵的類和這些類的基本功能
- ClassPathMapperScanner:繼承Spring的ClassPathBeanDefinitionScanner,作用是在classPath中掃描mapper以及掃描之後處理beanDefinition,mapperFactoryBean在這個類中的processBeanDefinitions方法中被加入beanDefinition
- MapperFactoryBean:繼承mybatis-spring的SqlSessionDaoSupport,實作了Spring的FactoryBean接口,作用是自定義bean,同時負責在mybatis的初始化結束之後添加mapper以及擷取mapper的代理對象
- MapperRegistry:mapper注冊器,添加和擷取mapper的實際類
- MapperProxy:mapper代理類,調用invoke方法來執行代理方法代理實際mapper中的操作及緩存mapper中的method
- MapperMethod:mapper方法類,包含目标方法對象和sql指令對象,主要作用是執行目标方法的sql語句
- SqlSessionTemplate:mybatis整合spring的核心類,這個類控制sqlSession,包含一個内部類用來執行動态代理