单独使用MyBatis时,mybatis对数据库如何进行管理?

情景介绍

因为入职某国企以后,做一个平台的二次开发,该平台是老外20年来前开发的一个平台,一直维护至今。该平台存储数据,采用的是SVN存储成一个个XML文件。其性能就不吐槽了,数据一上万,那性能跟屎一样
因为部分数据用原生平台的存储方式,已经无法满足了,因此决定引入数据库,当然,此前其他的项目也引入过数据库,不过那都是相当的惨烈,反正就是分分钟数据库就蹦了。
首先我们看下面这段代码
编写了一个工具类,一开始用起来没啥问题,可能有小伙伴问,你这写法有问题啊,你这个每次都要获取资源文件,然后构建会话工厂,然后在返回会话。好消耗性能啊。要怪就怪平台本身的性能实在太差了了,哪怕我在怎么慢,也远远比平台快啊。

 /**
     * @Description 获取不唯一的SqlSession,每一次调用,拿到回话都是不同的(自动提交事务)
     * @author hutao
     * @date 2020年1月15日
     */
    public static SqlSession getSqlSession() {
    	InputStream inputStream = null;
    	SqlSessionFactory sqlSessionFactory =null;
    	try {
    		inputStream = MybatisSqlSession.class.getResourceAsStream(sourcePath);
    		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		//自动提交
    		SqlSession openSession = sqlSessionFactory.openSession(true);
    		return openSession;
    	} catch (Exception e) {
    		logger.error("获取数据库连接会话失败,失败原因:{}",e);
    		e.printStackTrace();
    	}
    	return null;
    }

随着开发的功能越来越多,访问数据库越来越平凡,我发现连续点击的话,数据库连接就一直暴涨。
查看mysql数据库连接数

show PROCESSLIST;

**接着我做了第二版优化,单例模式+volatile **
OK,完美解决了连接数的问题,可是问题特码的又来了,我怎么保证我这个会话能够一直开启,不会被数据库单方面的关闭?于是我想,每次拿会话的时候判断下,会话是不是能用?于是我想用如下方法做个判断,如果被关闭了,我就重新打开,但是显然没有达到我要的效果,因为我在数据库里面强制关闭连接,程序里面拿到的任然是打开的。
uniqueSqlSession.getConnection().isClosed();

    private volatile static SqlSession uniqueSqlSession = null;
    /**
     * @Description 获取唯一 SqlSession回话,使用此方法,请保证数据库不会单方便关闭连接
     * @author hutao
     * @date 2020年1月15日
     */
    public static SqlSession getUniqueSqlSession(){
        if (uniqueSqlSession == null){
           synchronized (MybatisSqlSession.class){
               if (uniqueSqlSession == null){
            	   InputStream inputStream = null;
            	   SqlSessionFactory sqlSessionFactory =null;
            	   try {
            		   inputStream = MybatisSqlSession.class.getResourceAsStream(sourcePath);
            		   sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            		   //自动提交
            		   uniqueSqlSession = sqlSessionFactory.openSession(true);
            	   } catch (Exception e) {
            		   logger.error("获取数据库连接会话失败,失败原因:{}",e);
            		   e.printStackTrace();
            	   }
               }
           }
        }
        return uniqueSqlSession;
    }

后来实在是想,算了,反正这个项目百分之98以上都用不到数据库,浪费资源就浪费吧,那就去线程池去等着吧,反正在慢也比原生平台快。
随着我把数据库引进来以后,项目成员,用数据库越来越多,使用越来越频繁,知道5月底,才发现等待时间越来越长,看来还是得要好好的研究下了,因为这时候,使用数据库查询居然比原生平台那屎一样的性能还慢了,因为全在连接池排队去了,真是排队3分钟,查询5毫秒,能不慢吗?看来该是解读下mybatis的连接池原理了。
还记得我们配置mybatis-config.xml吗?
单独使用MyBatis时,mybatis对数据库如何进行管理?_第1张图片
MyBatis把数据源DataSource分为三种:

  • UNPOOLED 不使用连接池的数据源
  • POOLED 使用连接池的数据源
  • JNDI 使用JNDI实现的数据源
    单独使用MyBatis时,mybatis对数据库如何进行管理?_第2张图片
    现在就让我们来一起探究mybatis的POOLED吧
    1、首先我们先看看反编译后的PoolState
    单独使用MyBatis时,mybatis对数据库如何进行管理?_第3张图片
  • PooledDataSource将jConnection对象包裹成PooledConnection对象放到了PoolState类型的容器中维护;
  • MyBatis将连接池中的PooledConnection分为两种状态: 空闲状态(idle)和活动状态(active),
  • idleConnections:空闲(idle)状态PooledConnection对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从此集合中取PooledConnection对象。当用完一个java.sql.Connection对象时,MyBatis会将其包裹成PooledConnection对象放到此集合中。
  • activeConnections:活动(active)状态的PooledConnection对象被放置到名为activeConnections的ArrayList中,表示当前正在被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从idleConnections集合中取PooledConnection对象,如果没有,则看此集合是否已满,如果未满,PooledDataSource会创建出一个PooledConnection,添加到此集合中,并返回。
    2、接着我们来看看PooledDataSource的getConnection方法
    单独使用MyBatis时,mybatis对数据库如何进行管理?_第4张图片
    我们发现这个方法调用了
private PooledConnection popConnection(String username, String password) throws SQLException {}

单独使用MyBatis时,mybatis对数据库如何进行管理?_第5张图片
代码量有点多,让我们一步一步的消化。
3、popConnection

  • 3.1、如果空闲idleConnections里面有,我们就从空闲里面取, 在这里插入图片描述
  • 3.2、如果活动activeConnections数小于最大的活动限制,就创建一个

在这里插入图片描述

  • 3.3、如果活动activeConnections数已满,则判断最先进入连接池的PooledConnection对象,判断是否超过限制时间,如果超过限制时间,则声明为过期的会话,并且使用PoolConnection内部的realConnection重新生成一个PooledConnection。
    单独使用MyBatis时,mybatis对数据库如何进行管理?_第6张图片
  • 3.4、如果连接没有过期,则等待。

单独使用MyBatis时,mybatis对数据库如何进行管理?_第7张图片

  • 3.5、如果获取PooledConnection成功,则更新其信息,并添加到activeConnections中

单独使用MyBatis时,mybatis对数据库如何进行管理?_第8张图片
分析完mybatis的线程池,我们就开始我们的工作吧,编写一个mybatis的配置工具类(虽然最后发现好像对我写配置类也没暖用,就当研究了一次源码把)。
我们需要做如下几个思考:
1、保证会话工厂有且仅有一个,会话存在多个,
2、保证线程之间的会话互不影响
3、保证GC能够回收

import java.io.InputStream;
import java.sql.SQLException;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description mybatis会话配置
 * @author hutao
 * @mail [email protected]
 * @date 2020年6月6日
 */
public class MybatisConfig {
	
	private static Logger logger = LoggerFactory.getLogger(MybatisConfig.class);
	
	private static String sourcePath = "/com/sunwise/cascoalm/source/mybatis-config.xml";

	/**
	 * 整个项目只需要一个数据库会话工厂
	 */
	private static SqlSessionFactory uniqueSqlSessionFactory = null;
	
	/**
	 * 创建本地线程变量,为每一个线程独立管理一个session对象 每一个线程只有且仅有单独且唯一的一个session对象
	 * 加上线程变量对session进行管理,可以保证线程安全,避免多实例同时调用同一个session对象
	 * 每一个线程都会new一个线程变量,从而分配到自己的session对象
	 */
	private static ThreadLocal threadlocal = new ThreadLocal();
	
	 /**
     * @Description 获取唯一数据库会话工厂
     * @author hutao
     * @mail [email protected]
     * @date 2020年6月6日
     */
    private static SqlSessionFactory getSqlSessionFactory(){
        if (uniqueSqlSessionFactory == null){
           synchronized (MybatisSqlSession.class){
               if (uniqueSqlSessionFactory == null){
            	   InputStream inputStream = null;
            	   try {
            		   inputStream = MybatisSqlSession.class.getResourceAsStream(sourcePath);
            		   uniqueSqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            		   inputStream.close();
            	   } catch (Exception e) {
            		   logger.error("获取数据库连接会话工厂失败,失败原因:{}",e);
            		   e.printStackTrace();
            	   }
               }
           }
        }
        return uniqueSqlSessionFactory;
    }
    
	 /**
     * @Description 获取sqlSesion会话(优先从线程变量中取session对象)
     * @author hutao
	 * @throws SQLException 
     * @mail [email protected]
     * @param boolean auto false时需要自己手动提交事务
     * @date 2020年6月6日
     */
    public static SqlSession openSqlSession(Boolean auto) {
		SqlSession session = threadlocal.get();
		if(session==null){
			newSession(auto);
			session = threadlocal.get();
		}
		return session;
	}

    /**
     * @Description 新建session会话,并把session放在线程变量中
     * @author hutao
	 * @throws SQLException 
     * @mail [email protected]
     * @date 2020年6月6日
     */
	private static void newSession(Boolean auto) {
		getSqlSessionFactory();
		SqlSession session = null;
		if(auto==null) {
			session = uniqueSqlSessionFactory.openSession(true);
		}else {
			session = uniqueSqlSessionFactory.openSession(auto);
		}
		threadlocal.set(session);
	}
	
    /**
     * @Description 关闭SqlSession,GC回收
     * @author hutao
	 * @throws SQLException 
     * @mail [email protected]
     * @date 2020年6月6日
     */
	public static void closeSqlSession(){
		SqlSession sqlSession = threadlocal.get();
		//如果SqlSession对象非空
		if(sqlSession != null){
			sqlSession.close();
			//分离线程和和会话关系,让JVM回收
			threadlocal.remove();
		}
	}
}

使用示例

    private static Map>  system = new ConcurrentHashMap<>();

	/**
	 * Description: 获取系统配置常量
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年6月6日 
	 */
	@Override
	public List getAlmCascoSystem(String keyName)throws Exception {
		if(system.get(keyName) != null) {
			return system.get(keyName);
		}
		ProjectMapper projectMapper = MybatisConfig.openSqlSession(true).getMapper(ProjectMapper.class);
		try {
			List listKeyName = projectMapper.queryAlmCascoSystem(keyName);
			system.put(keyName, listKeyName);
			return system.get(keyName);
		} catch (Exception e) {
			throw e;
		}finally {
			MybatisConfig.closeSqlSession();
		}
	}

你可能感兴趣的:(程序员)