sqlsession、sqlsessionManager以及sqlsessionTemplate的理解

sqlsession、sqlsessionManager以及sqlsessionTemplate的理解sqlSession是mybatis的核心操作类,其中对数据库的crud都封装在这个中,是一个顶级接口,其中默认实现类是DefaultSqlSession这个类,为什么说DefaultSqlsession不是线程安全的?首先我们都知道mybatis在底层都是使用的JDBC,而JDBC这本来就是线程不安全的(连接对象Connection只有一个),所以我们只要关注session和co…

大家好,又见面了,我是你们的朋友全栈君。

sqlSession

是mybatis的核心操作类,其中对数据库的crud都封装在这个中,是一个顶级接口,其中默认实现类是DefaultSqlSession这个类,

为什么说DefaultSqlsession不是线程安全的?

首先我们都知道mybatis在底层都是使用的JDBC,而JDBC这本来就是线程不安全的(连接对象Connection只有一个),所以我们只要关注session和connnect的关系就好了

首先是一段最普通的mybatis生成sqlSession的代码:

SqlSession session = null;
String resource = "configuration.xml";
// 使用io流读取配置
InputStream inputStream;
inputStream = Resources.getResourceAsStream(resource);
//这里是解析配置文件
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 得到了一个会话,有了这个会话,你就可以对数据进行增,删,改,查的操作
session = sqlSessionFactory.openSession();

在此时sqlsessionFactory打开(创建了一个sqlsession会话),下面我们来看看这个session是怎么产生的:

//这个是org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
//之后走到该类的这个方法里来(openSessionFromDataSource)
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      //开始创建事物
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //将事物传递给执行器Executor,这个是session执行数据库操作的核心(有三种执行器类型)
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

最后sqlsession执行都是通过执行器执行的,默认执行器是SimpleExecutor,她通过连接Connection这个类创建了Statement这个JDBC要用到的对象,开始走JDBC的流程:

//查询方法
@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);
      // 这里创建statement对象,这个方法中就用到了Connection连接对象,此时我们主要看这个方法中Connection的创建时怎么样的
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

prepareStatement(handler, ms.getStatementLog())方法解析(重点看Connection他是怎么拿的)

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;
  }
  //由这个方法可以看出,具体实现是transaction.getConnection();
  protected Connection getConnection(Log statementLog) throws SQLException {
    // 这里最终同通过创建Executor时传入的transcation进行了连接获取
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

继续看transaction.getConnection();

//可以看出他只会产生一个连接 
@Override
  public Connection getConnection() throws SQLException {
    // 这里只要有连接了就不重新打开连接了(从数据源中再次获取),说明只能有一个连接在一个org.apache.ibatis.transaction.Transaction中
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

最终可以看出一次SqlSession的执行最终只会产生一个connection,所以我们设想一下,在两个线程通过同一个sqlsession来执行crud,那么就有可能,我先跑完的线程,把唯一的这一个连接给关闭掉,从而造成另一条线程的逻辑不被成功执行,所以通过DefaultSqlSession来执行数据库操作是线程不安全的

sqlsessionTemplate

为什么说sqlsessionTemplate是线程安全的?

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  private final PersistenceExceptionTranslator exceptionTranslator;
  
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }
  ..........
      
  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;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
    }
  }

从这个构造方法可以看出,sqlsessionTemplate传参是必须需要一个sqlsessionfactory的,sqlsessionTemplate在执行crud操作时,都不是通过唯一的一个sqlsession来执行的,他都是通过动态代理来执行具体的操作的,所以多个线程持有同一个sqlsessionTemplate是不会产生线程安全问题的。

sqlSessionManager

首先我们来看看这个类实现的接口

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  .....
  public static SqlSessionManager newInstance(Reader reader) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }

  public static SqlSessionManager newInstance(Reader reader, String environment) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
  }

  public static SqlSessionManager newInstance(Reader reader, Properties properties) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));
  }
}

可以看出他的一个必要的参数也是sqlsessionFactory,SqlSessionManager既实现了SqlSessionFactory,也实现了SqlSession,具备生产SqlSession的能力,也具备SqlSession的能力,SqlSession的作用是执行具体的Sql语句。

sqlsessionManager他把构造方法私有化了,想要创建一个sqlsessionManager对象,你只能调用newInstance()来创建一个SqlsessionManager对象,下面来看连接对象Connection他是怎么获取的

public Connection getConnection() {
    SqlSession sqlSession = (SqlSession)this.localSqlSession.get();
    if (sqlSession == null) {
        throw new SqlSessionException("Error:  Cannot get connection.  No managed session is started.");
    } else {
        return sqlSession.getConnection();
    }
}

首先我解释一下localSqlSession,这个属性其实就是一个ThreadLocal类,可以为每一个线程分配一个副本对象,来保证线程安全。

从这个方法我们可以看出每个线程都会被分配一个对应的对象副本,而且这个是保证了Connection对象线程的安全性,下面我们来看看具体执行是什么样子的:

private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
}
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
}

public <T> Cursor<T> selectCursor(String statement) {
   return this.sqlSessionProxy.selectCursor(statement);
}

public <T> Cursor<T> selectCursor(String statement, Object parameter) {
    return this.sqlSessionProxy.selectCursor(statement, parameter);
}

眼熟吧,标准的代理模式,和sqlsessionTemplate类似,具体操作数据库的都是通过产生的动态代理对象去执行的。

测试

@Component
public class TestSqlSessionManager {

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;
//    @Autowired
//    private SqlSessionFactory sqlSessionFactory;//这个是一个接口,别想多了你
    @Autowired
    private SqlSessionManager sqlSessionManager;

    private static ApplicationContext context;

    private static String namespace = "com.swjd.dao.DashboardDao";

    static {
        TestSqlSessionManager.context = new ClassPathXmlApplicationContext(
                "spring/applicationContext-dao.xml","spring/applicationContext-service.xml",
                "spring/applicaitonContext-transaction.xml");
    }

    @Test
    public void bTest(){
        //三个类
        SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class);
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        SqlSessionManager sqlSessionManager = context.getBean(SqlSessionManager.class);
            List<Object> list = sqlSessionTemplate.selectList(namespace + ".selectByExample");
//            SqlSession sqlSession = sqlSessionManager.openSession();
//            List<Object> objects = sqlSession.selectList(namespace + ".selectByExample");
            //使用多线程测试
            //测试sqlsessionFactory
            for (int i=0;i<4;i++){
                int index = i;
                Thread thread=new Thread(() ->{
                    try {
                        Class<? extends SqlSessionTemplate> aClass = sqlSessionTemplate.getClass();
                        Field sqlSessionProxy = aClass.getDeclaredField("sqlSessionProxy");
                        sqlSessionProxy.setAccessible(true);
                        Object o1 = sqlSessionProxy.get(sqlSessionTemplate);
                        System.out.println("A第"+ index +"个===================="+o1.hashCode());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    List<Object> objects = sqlSessionTemplate.selectList(namespace + ".selectByExample");
                });
                thread.start();
            }
            //测试sqlsessionManager
            for (int i=0;i<4;i++){
                int index = i;
                Thread thread=new Thread(() ->{
                    SqlSession sqlSession = sqlSessionManager.openSession();
                    try {
                        Class<? extends SqlSessionManager> aClass = sqlSessionManager.getClass();
                        Field sqlSessionProxy = aClass.getDeclaredField("sqlSessionProxy");
                        sqlSessionProxy.setAccessible(true);
                        Object o2 = sqlSessionProxy.get(sqlSessionManager);
                        System.out.println("B第"+ index +"个===================="+o2.hashCode());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    List<Object> objects = sqlSessionManager.selectList(namespace + ".selectByExample");
                });
                thread.start();
            }
    }
}

测试结果如图:

sqlsession、sqlsessionManager以及sqlsessionTemplate的理解

总结

  1. DefaultSqlSession的内部没有提供像SqlSessionManager一样通过ThreadLocal的方式来保证线程的安全性;
  2. SqlSessionManager是通过localSqlSession这个ThreadLocal变量,记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一个线程多次创建SqlSession对象造成的性能损耗;
  3. DefaultSqlSession不是线程安全的,我们在进行原生开发的时候想要达到线程安全的话,那就需要每次为一个操作都创建一个SqlSession对象,其性能可想而知

疑惑

JDK动态代理创建的对象会占用内存吗?如果会占用内存的话,那么创建动态代理对象也会是一个吃内存的操作,那么在内存方面,sqlsessionTemplate和sqlsessionManager都会是特别不友好的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140588.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • SQLyog下载、安装和破解(亲测永久有效)

    SQLyog下载、安装和破解(亲测永久有效)百度网盘下载链接:https://pan.baidu.com/s/1xck24MsW7y9Gi8ZvDgDMEg密码:ba2x版本2链接:https://pan.baidu.com/s/1-0POHrvx5vM04bKZq9DmIQ密码:o268安装非常之简单,直接点击exe文件安装,最后输入破解序列号即可永久使用。更多资料,搜索或扫码关注公众号:数说Cloud…

  • Redis删除策略和淘汰策略[通俗易懂]

    Redis删除策略和淘汰策略[通俗易懂]来的问题,Redis引入了定期删除策略(是他们的一个比较折中的方案)周期性轮询Redis库中的时效性数据,采取随机抽取的策略,利用过期数据占比的方式控制删除频度。实际上,在前面所说的删除策略,它针对的是expire命令进行的操作,也就是说那些具有时效性的数据(已经过期,并且还在占用内存的数据),我们在这里说的是针对那些并没有过期,或者是内存中的数据没有一个带有有效期,全是永久性数据,这时候删除策略就不起作用了,所以这个时候内存满了我们再去插入数据到内存是怎么做?删除这批key中已过期的。……

  • hash一致性算法以及应用场景_什么不是算法的基本特性

    hash一致性算法以及应用场景_什么不是算法的基本特性最近有小伙伴跑过来问什么是Hash一致性算法,说面试的时候被问到了,因为不了解,所以就没有回答上,问我有没有相应的学习资料推荐,当时上班,没时间回复,晚上回去了就忘了这件事,今天突然看到这个,加班为大家整理一下什么是Hash一致性算法,希望对大家有帮助!文末送书,长按抽奖助手小程序即可参与,祝君好运!经常阅读我文章的小伙伴应该都很熟悉我写文章的套路,上来就是先要问一句为什么?也就是为什么要有Has

  • C语言实现PID算法:位置式PID和增量式PID[通俗易懂]

    原创者微信公众号PID算法可以说是在自动控制原理中比较经典的一套算法,在现实生活中应用的比较广泛。大学参加过电子竞赛的朋友都应该玩过电机(或者说循迹小车),我们要控制电机按照设定的速度运转,PID控制在其中起到了关键的作用。说来惭愧,大学这门课程学的不咋滴,老师讲的课基本没听进去过。直到后面接触竞赛,算是对PID有了很基础的一点点认识,直到现在工作实际应用的…

  • Linux Bash漏洞修复

    Linux Bash漏洞修复特别提醒:Linux官方已经给出最新解决方案,已经解决被绕过的bug,建议您尽快重新完成漏洞修补。openSUSE镜像已经给出修复方案了。【已确认被成功利用的软件及系统】所有安装GNUbash版本小于或者等于4.3的Linux操作系统。【漏洞描述】该漏洞源于你调用的bashshell之前创建的特殊的环境变量,这些变量可以包含代码,同时会被bash执行。【漏洞检测方法】漏洞检测命令:env-iX='(){(a)=>\’bash…

    2022年10月26日
  • 浏览器渲染原理解析建议收藏

    Web页面运行在各种各样的浏览器当中,浏览器载入、渲染页面的速度直接影响着用户体验简单地说,页面渲染就是浏览器将html代码根据CSS定义的规则显示在浏览器窗口中的这个过程。先来大致了解一下浏览器都是

    2021年12月21日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号