ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

【MyBatis】Spring集成原理(二):创建 SqlSession

2020-12-18 21:31:35  阅读:377  来源: 互联网

标签:sqlSessionFactory SqlSessionTemplate Spring sqlSession selectOne SqlSession MyBa


我们现在已经有一个DefaultSqlSessionFactory,按照编程式的开发过程,我们接下来就会创建一个 SqlSession 的实现类,但是在 Spring 里面,我们不是直接使用 DefaultSqlSession 的,而是对它进行了一个封装,这个 SqlSession 的实现类就是SqlSessionTemplate。这个跟 Spring 封装其他的组件是一样的,比如 JdbcTemplate,RedisTemplate 等等,也是 Spring 跟 MyBatis 整合的最关键的一个类。

为什么不用直接使用 DefaultSqlSession?
DefaultSqlSession 是线程不安全的。因为 SqlSession 的生命周期是请求和操作(Request/Method),所以我们会在每次请求到来(一个请求一般会执行多条sql)的时候都创建一个 DefaultSqlSession(多例,线程安全)

而从 SqlSessionTemplate 的类注释中,我们可以看到它是线程安全的,,Spring IOC 容器中只有一个 SqlSessionTemplate(默认单例)。
在这里插入图片描述

SqlSessionTemplate

SqlSessionTemplate 实现了 SqlSession 接口,所以跟 DefaultSqlSession 有一样的方法:selectOne()、selectList()、insert()、update()、delete()… 不过所有方法的实现都是通过一个代理对象:

在这里插入图片描述
这个代理对象在 SqlSessionTemplate 构造方法里面通过一个代理类创建:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
	
    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 基于JDK动态代理创建代理对象
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

所以,当调用 SqlSessionTemplate 中的方法时,它们都会走到内部代理类 SqlSessionInterceptor 的 invoke() 方法

// SqlSessionTemplate的内部类
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 首先会使用工厂类、执行器类型、异常解析器创建一个 sqlSession,
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 然后再调用sqlSession的实现类,实际上就是在这里调用了DefaultSqlSession的方法。
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof 
            PersistenceException) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.
              translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

按照编程式使用的套路,拿到 SqlSession 后就可以进行 SqlSession#selectOne() 进行使用了。那在 Spring 中,我们如何在Dao层拿到一个 SqlSessionTemplate 实例?

SqlSessionDaoSupport

MyBatis里面提供了一个SqlSessionDaoSupport,里面持有一个SqlSessionTemplate 对象,并且提供了一个 getSqlSession()方法,让我们获得一个SqlSessionTemplate。

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;
  
  // 用户在使用时应该先通过该方法传入 SqlSessionFactory,然后得到一个 SqlSessionTemplate
  // 注:传入的 SqlSessionFactory 是通过xml中配置的 SqlSessionFactoryBean 创建的 SqlSessionFactory 实例
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  // 用户通过此方法去获取 SqlSession
  // 注:实际上返回的是上面 setSqlSessionFactory 创建的 SqlSessionTemplate
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

也就是说我们让 DAO 层的实现类继承 SqlSessionDaoSupport,就可以获得SqlSessionTemplate,然后在里面封装SqlSessionTemplate的方法。但为了减少重复的代码,我们通常不会让我们的实现类直接去继承SqlSessionDaoSupport,而是先创建一个BaseDao继承 SqlSessionDaoSupport。

1)在BaseDao里面封装对数据库的操作,包括selectOne()、selectList()、 insert()、delete()这些方法,子类就可以直接调用。

public class BaseDao extends SqlSessionDaoSupport { 

    // 在IOC容器获取 SqlSessionFactory
    // 注:这里实际是通过xml中配置的 SqlSessionFactoryBean 创建的 SqlSessionFactory 实例
    @Autowired 
    private SqlSessionFactory sqlSessionFactory;
	@Autowired
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { 
        super.setSqlSessionFactory(sqlSessionFactory);
    }
	public Object selectOne(String statement, Object parameter) { 
        // 调用SqlSessionDaoSupport的getSqlSession方法获取到SqlSessionTemplate
        return getSqlSession().selectOne(statement, parameter); 
    } 
    // .......
}

2)让我们的实现类继承BaseDao并且实现我们的DAO层接口,这里就是我们的Mapper接口。实现类需要加上@Repository的注解。在实现类的方法里面,我们可以直接调用父类(BaseDao)封装的selectOne()方法,那么它最终会调用sqlSessionTemplate的selectOne()方法。

@Repository 
public class EmployeeDaoImpl extends BaseDao implements EmployeeMapper {
    @Override 
    public Employee selectByPrimaryKey(Integer empId) {
    	// 最后会执行 sqlSession.selectOne("com.my.dao.EmployeeMapper.selectById",empId); 
        Employee emp = (Employee) this.selectOne("com.my.dao.EmployeeMapper.selectById",empId); 
        return emp; 
    } 
    // ......
}

3)在需要使用的地方,比如Service层,注入我们的实现类,调用实现类的方法就行了。我们这里直接在单元测试类里面注入:

@Autowired 
EmployeeDaoImpl employeeDao;
@Test 
public void EmployeeDaoSupportTest() { 
    // 最终会调用到DefaultSqlSession的方法。
    System.out.println(employeeDao.selectById(1)); 
} 

虽然这样也能完数据库操作,但是仍然存在问题:

  1. 代码多:我们的每一个DAO层的接口(Mapper接口也属于),如果要拿到一个 SqlSessionTemplate 去操作数据库,都要创建实现一个实现类,加上@Repository的注解,继承BaseDao,这个工作量也不小。
  2. 硬编码:我们去直接调用 selectOne() 方法,还是出现了 StatementID 的硬编码,并且 MyBatis 内部基于接口的动态代理 MapperProxy 在这里根本没用上。

所以,我们能不能通过什么方式实现以下两个目标?

  1. 不创建任何的实现类,而是通过 @Autowired 直接将 Mapper 注入到要使用的地方,并且可以拿到 SqlSessionTemplate 去操作数据库
  2. 当执行数据库操作的时候不是硬编码,而是是基于 MapperProxy 动态生成实现对象

请看下一篇

标签:sqlSessionFactory,SqlSessionTemplate,Spring,sqlSession,selectOne,SqlSession,MyBa
来源: https://blog.csdn.net/weixin_43935927/article/details/111321532

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有