sqlsession类源码_sqlsession使用

hacker|
118

文章目录:

怎么没有mybatis源码解析相关的文档

我们还记得是这样配置sqlSessionFactory的:

[java] view plain copy

bean id="sqlSessionFactory" class="99fe-eca4-bc9a-335b org.mybatis.spring.SqlSessionFactoryBean"

property name="dataSource" ref="dataSource" /

property name="configLocation" value="classpath:configuration.xml"/property

property name="mapperLocations" value="classpath:com/xxx/mybatis/mapper/*.xml"/

property name="typeAliasesPackage" value="com.tiantian.mybatis.model" /

/bean

这里配置了一个mapperLocations属性,它是一个表达式,sqlSessionFactory会根据这个表达式读取包com.xxx.mybaits.mapper下面的所有xml格式文件,那么具体是怎么根据这个属性来读取配置文件的呢?

答案就在SqlSessionFactoryBean类中的buildSqlSessionFactory方法中:

[java] view plain copy

if (!isEmpty(this.mapperLocations)) {

for (Resource mapperLocation : this.mapperLocations) {

if (mapperLocation == null) {

continue;

}

try {

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),

configuration, mapperLocation.toString(), configuration.getSqlFragments());

xmlMapperBuilder.parse();

} catch (Exception e) {

throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);

} finally {

ErrorContext.instance().reset();

}

if (logger.isDebugEnabled()) {

logger.debug("Parsed mapper file: '" + mapperLocation + "'");

}

}

}

mybatis使用XMLMapperBuilder类的实例来解析mapper配置文件。

[java] view plain copy

public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, MapString, XNode sqlFragments) {

this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),

configuration, resource, sqlFragments);

}

[java] view plain copy

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, MapString, XNode sqlFragments) {

super(configuration);

this.builderAssistant = new MapperBuilderAssistant(configuration, resource);

this.parser = parser;

this.sqlFragments = sqlFragments;

this.resource = resource;

}

接着系统调用xmlMapperBuilder的parse方法解析mapper。

[java] view plain copy

public void parse() {

//如果configuration对象还没加载xml配置文件(避免重复加载,实际上是确认是否解析了mapper节点的属性及内容,

//为解析它的子节点如cache、sql、select、resultMap、parameterMap等做准备),

//则从输入流中解析mapper节点,然后再将resource的状态置为已加载

if (!configuration.isResourceLoaded(resource)) {

configurationElement(parser.evalNode("/mapper"));

configuration.addLoadedResource(resource);

bindMapperForNamespace();

}

//解析在configurationElement函数中处理resultMap时其extends属性指向的父对象还没被处理的resultMap节点

parsePendingResultMaps();

//解析在configurationElement函数中处理cache-ref时其指向的对象不存在的cache节点(如果cache-ref先于其指向的cache节点加载就会出现这种情况)

parsePendingChacheRefs();

//同上,如果cache没加载的话处理statement时也会抛出异常

parsePendingStatements();

}

mybatis解析mapper的xml文件的过程已经很明显了,接下来我们看看它是怎么解析mapper的:

[java] view plain copy

private void configurationElement(XNode context) {

try {

//获取mapper节点的namespace属性

String namespace = context.getStringAttribute("namespace");

if (namespace.equals("")) {

throw new BuilderException("Mapper's namespace cannot be empty");

}

//设置当前namespace

builderAssistant.setCurrentNamespace(namespace);

//解析mapper的cache-ref节点

cacheRefElement(context.evalNode("cache-ref"));

//解析mapper的cache节点

cacheElement(context.evalNode("cache"));

//解析mapper的parameterMap节点

parameterMapElement(context.evalNodes("/mapper/parameterMap"));

//解析mapper的resultMap节点

resultMapElements(context.evalNodes("/mapper/resultMap"));

//解析mapper的sql节点

sqlElement(context.evalNodes("/mapper/sql"));

//使用XMLStatementBuilder的对象解析mapper的select、insert、update、delete节点,

//mybaits会使用MappedStatement.Builder类build一个MappedStatement对象,

//所以mybaits中一个sql对应一个MappedStatement

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

} catch (Exception e) {

throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);

}

}

configurationElement函数几乎解析了mapper节点下所有子节点,至此mybaits解析了mapper中的所有节点,并将其加入到了Configuration对象中提供给sqlSessionFactory对象随时使用。这里我们需要补充讲一下mybaits是怎么使用XMLStatementBuilder类的对象的parseStatementNode函数借用MapperBuilderAssistant类对象builderAssistant的addMappedStatement解析MappedStatement并将其关联到Configuration类对象的:

[java] view plain copy

public void parseStatementNode() {

//ID属性

String id = context.getStringAttribute("id");

//databaseId属性

String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {

return;

}

//fetchSize属性

Integer fetchSize = context.getIntAttribute("fetchSize");

//timeout属性

Integer timeout = context.getIntAttribute("timeout");

//parameterMap属性

String parameterMap = context.getStringAttribute("parameterMap");

//parameterType属性

String parameterType = context.getStringAttribute("parameterType");

Class? parameterTypeClass = resolveClass(parameterType);

//resultMap属性

String resultMap = context.getStringAttribute("resultMap");

//resultType属性

String resultType = context.getStringAttribute("resultType");

//lang属性

String lang = context.getStringAttribute("lang");

LanguageDriver langDriver = getLanguageDriver(lang);

Class? resultTypeClass = resolveClass(resultType);

//resultSetType属性

String resultSetType = context.getStringAttribute("resultSetType");

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

String nodeName = context.getNode().getNodeName();

SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

//是否是select节点

boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

//flushCache属性

boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);

//useCache属性

boolean useCache = context.getBooleanAttribute("useCache", isSelect);

//resultOrdered属性

boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// Include Fragments before parsing

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);

includeParser.applyIncludes(context.getNode());

// Parse selectKey after includes and remove them.

processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: selectKey and include were parsed and removed)

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

//resultSets属性

String resultSets = context.getStringAttribute("resultSets");

//keyProperty属性

String keyProperty = context.getStringAttribute("keyProperty");

//keyColumn属性

String keyColumn = context.getStringAttribute("keyColumn");

KeyGenerator keyGenerator;

String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;

keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

if (configuration.hasKeyGenerator(keyStatementId)) {

keyGenerator = configuration.getKeyGenerator(keyStatementId);

} else {

//useGeneratedKeys属性

keyGenerator = context.getBooleanAttribute("useGeneratedKeys",

configuration.isUseGeneratedKeys() SqlCommandType.INSERT.equals(sqlCommandType))

? new Jdbc3KeyGenerator() : new NoKeyGenerator();

}

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,

fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,

resultSetTypeEnum, flushCache, useCache, resultOrdered,

keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

}

由以上代码可以看出mybaits使用XPath解析mapper的配置文件后将其中的resultMap、parameterMap、cache、statement等节点使用关联的builder创建并将得到的对象关联到configuration对象中,而这个configuration对象可以从sqlSession中获取的,这就解释了我们在使用sqlSession对数据库进行操作时mybaits怎么获取到mapper并执行其中的sql语句的问题。

如何使用SqlSessionTemplate

工作中,需要学习一下MyBatis sqlSession的产生过程,翻看了mybatis-spring的源码,阅读了一些mybatis的相关doc,对mybatis sqlSession有了一些认知和理解,这里简单的总结和整理一下。

首先, 通过翻阅源码,我们来整理一下mybatis进行持久化操作时重要的几个类:

SqlSessionFactoryBuilder:build方法创建SqlSessionFactory实例。

SqlSessionFactory:创建SqlSession实例的工厂。

SqlSession:用于执行持久化操作的对象,类似于jdbc中的Connection。

SqlSessionTemplate:MyBatis提供的持久层访问模板化的工具,线程安全,可通过构造参数或依赖注入SqlSessionFactory实例。

Hibernate是与MyBatis类似的orm框架,这里与Hibernate进行一下对比,Hibernate中对于connection的管理,是通过以下几个重要的类:

SessionFactory:创建Session实例的工厂,类似于MyBatis中的SqlSessionFactory。

Session:用来执行持久化操作的对象,类似于jdbc中的Connection。

HibernateTemplate:Hibernate提供的持久层访问模板化的工具,线程安全,可通过构造参数或依赖注入SessionFactory实例。

在日常的开发中,我们经常需要这样对MyBatis和Spring进行集成,把sqlSessionFactory交给Spring管理,通常情况下,我们这样配置:

?

1

2

3

bean id="sqlSessionFactory" class="eca4-bc9a-335b-3d3a org.mybatis.spring.SqlSessionFactoryBean"

property name="dataSource" ref="dataSource" /

/bean

通过上面的配置,Spring将自动创建一个SqlSessionFactory对象,其中使用到了org.mybatis.spring.SqlSessionFactoryBean,其 是MyBatis为Spring提供的用于创建SqlSessionFactory的类,将在Spring应用程序的上下文建议一下可共享的 MyBatis SqlSessionFactory实例,我们可以通过依赖注入将SqlSessionFactory传递给MyBatis的一些接口。

如果通过Spring进行事务的管理,我们需要增加Spring注解的事务管理机制,如下配置:

?

1

2

3

4

5

bean id="transactionManager" class="bc9a-335b-3d3a-426b org.springframework.jdbc.datasource.DataSourceTransactionManager"

property name="dataSource" ref="dataSource" /

/bean

tx:annotation-driven/

这样,我们就可以使用Spring @Transactional注解,进行事务的控制,表明所注释的方法应该在一个事务中运行。 Spring将在事务成功完成后提交事务,在事务发生错误时进行异常回滚,而且,Spring会将产生的MyBatis异常转换成适当的 DataAccessExceptions,从而提供具体的异常信息。

下面,我们通过分析SqlSessionUtils中getSession的源码,来详细的了解一下sqlSession的产生过程,源码如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, "No SqlSessionFactory specified");

notNull(executorType, "No ExecutorType specified");

SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);

if (holder != null holder.isSynchronizedWithTransaction()) {

if (holder.getExecutorType() != executorType) {

throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");

}

holder.requested();

if (logger.isDebugEnabled()) {

logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");

}

return holder.getSqlSession();

}

if (logger.isDebugEnabled()) {

logger.debug("Creating a new SqlSession");

}

SqlSession session = sessionFactory.openSession(executorType);

// Register session holder if synchronization is active (i.e. a Spring TX is active)

//

// Note: The DataSource used by the Environment should be synchronized with the

// transaction either through DataSourceTxMgr or another tx synchronization.

// Further assume that if an exception is thrown, whatever started the transaction will

// handle closing / rolling back the Connection associated with the SqlSession.

if (isSynchronizationActive()) {

Environment environment = sessionFactory.getConfiguration().getEnvironment();

if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {

if (logger.isDebugEnabled()) {

logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");

}

holder = new SqlSessionHolder(session, executorType, exceptionTranslator);

bindResource(sessionFactory, holder);

registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));

holder.setSynchronizedWithTransaction(true);

holder.requested();

} else {

if (getResource(environment.getDataSource()) == null) {

if (logger.isDebugEnabled()) {

logger.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");

}

} else {

throw new TransientDataAccessResourceException(

"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");

}

}

} else {

if (logger.isDebugEnabled()) {

logger.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");

}

}

return session;

}

上面的getSession方法,会从Spring的事务管理器中获取一个SqlSession或创建一个新的SqlSession,将试图从当前事务中得到一个SqlSession,然后,如果配置有事务管理器的工厂并且Spring 的事务管理器是活跃的,它将会锁定当前事务的SqlSession,保证同步。主要是通过以下几个步骤进行SqlSession的创建:

它会首先获取SqlSessionHolder,SqlSessionHolder用于在TransactionSynchronizationManager中保持当前的SqlSession。

如果holder不为空,并且holder被事务锁定,则可以通过holder.getSqlSession()方法,从当前事务中获取sqlSession,即 Fetched SqlSession from current transaction。

如果不存在holder或没有被事务锁定,则会创建新的sqlSession,即 Creating a new SqlSession,通过sessionFactory.openSession()方法。

如果当前线程的事务是活跃的,将会为SqlSession注册事务同步,即 Registering transaction synchronization for SqlSession。

mybatis sqlSession.getMaper方法的返回类型

没有看过源码,但是这种类型的方法都是通过反射来实现的,即你传了一个对象的class过去,就可以通过反射方式来生成这个对象,所以这个方法的返回值类型跟你传的参数有关。

初看Mybatis 源码 SQL是怎么执行的

Mybatis通过这种方式实现了我们通过getMapper方式得到的Dao接口,可以直接通过接口的没有实现的方法来执行sql。

AuthUserDao mapper = session.getMapper(AuthUserDao.class);

getMapper方法到底做了什么。跟踪getMapper方法,进入到 MapperProxyFactory 类的 newInstance(SqlSession sqlSession) 方法。

@SuppressWarnings("unchecked")

protected T newInstance(MapperProxyT mapperProxy) {

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

public T newInstance(SqlSession sqlSession) {

final MapperProxyT mapperProxy = new MapperProxyT(sqlSession, mapperInterface, methodCache);

return newInstance(mapperProxy);

}

看过上一篇的例子,我们知道。最后是通过Proxy.newProxyInstance 来实现切入sql的。他的实现在MapperProxy的 invoke 方法里面。看下invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (Object.class.equals(method.getDeclaringClass())) {

return method.invoke(this, args);

}

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

简单来说,在invoke里面做了如下事情:

1. 通过我们调用的方法,如本例中的 selectAuthUserByName ,接口名来组合成语句。本例中是 com.mybatis.dao.AuthUserDao.selectAuthUserByName 。其实使用过sqlSession的selectOne(String statmet)之类的语句都知道。这个可以唯一定位到我们在sql映射文件中配置的sql语句

2. 通过返回值类型,定位到的语句的类型。确定最后应该执行的方法。是执行查询、删除、添加、修改等等。

看下mybatis的是如何做的:

public Object execute(SqlSession sqlSession, Object[] args) {

Object result;

if (SqlCommandType.INSERT == command.getType()) {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.insert(command.getName(), param));

} else if (SqlCommandType.UPDATE == command.getType()) {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.update(command.getName(), param));

} else if (SqlCommandType.DELETE == command.getType()) {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.delete(command.getName(), param));

} else if (SqlCommandType.SELECT == command.getType()) {

if (method.returnsVoid() method.hasResultHandler()) {

executeWithResultHandler(sqlSession, args);

result = null;

} else if (method.returnsMany()) {

result = executeForMany(sqlSession, args);

} else if (method.returnsMap()) {

result = executeForMap(sqlSession, args);

} else {

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

}

} else {

throw new BindingException("Unknown execution method for: " + command.getName());

}

if (result == null method.getReturnType().isPrimitive() !method.returnsVoid()) {

throw new BindingException("Mapper method '" + command.getName()

+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");

}

return result;

}

可以看到的是,他是通过我们在sql配置文件中的语句类型来判断是执行哪种操作。然后通过返回类型来确定查询是查一条还是查多条记录。最后这种操作的方式,也是回归到了sqlSession中的CRUD的操作的方法。

下面看下,一条sql语句到底是怎么执行的?我们知道Mybatis其实是对JDBC的一个封装。假如我执行

session.update("com.mybatis.dao.AuthUserDao.updateAuthUserEmailByName", test@email.com);

语句,追踪下来,Executor、 BaseStatementHandler等等。在 SimpleExecutor 中有如下代码:

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {

Statement stmt = null;

try {

Configuration configuration = ms.getConfiguration();

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

stmt = prepareStatement(handler, ms.getStatementLog());

return handler.update(stmt);

} finally {

closeStatement(stmt);

}

}

1. 首先获取相关配置信息,这个在初始化时,从配置文件中解析而来

2. 新建了一个handler

3. 做了执行statement之前的准备工作。看看准备了些什么,跟踪代码,最后进入了DataSource类的doGetConnection方法,该方法做如下操作:

private Connection doGetConnection(Properties properties) throws SQLException {

initializeDriver();

Connection connection = DriverManager.getConnection(url, properties);

configureConnection(connection);

return connection;

}

private synchronized void initializeDriver() throws SQLException {

if (!registeredDrivers.containsKey(driver)) {

Class? driverType;

try {

if (driverClassLoader != null) {

driverType = Class.forName(driver, true, driverClassLoader);

} else {

driverType = Resources.classForName(driver);

}

// DriverManager requires the driver to be loaded via the system ClassLoader.

//

Driver driverInstance = (Driver)driverType.newInstance();

DriverManager.registerDriver(new DriverProxy(driverInstance));

registeredDrivers.put(driver, driverInstance);

} catch (Exception e) {

throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);

}

}

}

原来是通过prepareStatement 来执行了 我们初始化jdbc的操作。Class.forName DriverManager.getConnection. 这两步是在这里面完成的。

4. 将执行sql的部分交给handler

继续跟踪handler 可以看到SimpleStatementHandler 中。如下执行这个update语句

public int update(Statement statement)

throws SQLException {

String sql = boundSql.getSql();

Object parameterObject = boundSql.getParameterObject();

KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();

int rows;

if (keyGenerator instanceof Jdbc3KeyGenerator) {

statement.execute(sql, Statement.RETURN_GENERATED_KEYS);

rows = statement.getUpdateCount();

keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);

} else if (keyGenerator instanceof SelectKeyGenerator) {

statement.execute(sql);

rows = statement.getUpdateCount();

keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);

} else {

statement.execute(sql);

rows = statement.getUpdateCount();

}

return rows;

}

这边就完成了statement的操作,整个过程就是我们Jdbc的过程。原来真的就是对JDBC的简单封装

如何实现mybatis的sqlsessiontemplate

SqlSession sqlSession = null;

try {

sqlSession = sqlSessionFactory.openSession();

//namespace+id

sqlSession.insert("cn.jarjar.dao.BlogMapper.insertBlog", blog);

sqlSession.commit(true)

} catch (Exception e) {

e.printStackTrace();

sqlSession.rollback(true);

} finally {

sqlSession.close();

}

也就是要像原始的java.sql.Connection对象一样,必须按照:新建连接-执行SQL-提交(查询不需要)-如果操作数据存在异常需要回滚-释放数据库连接。注意第一点和最后一点,每个SqlSession新建之后必须释放,不然会造成数据库连接泄露的危险。也就是意味着SqlSession是个有状态的对象,是无法进行复用的,所以只能局限于request或者方法的范围,也就是所谓的线程不安全。

现象2:如果使用spring集成mybatis,官方提供了整和包mybatis-spring.jar,如果完成配置之后,使用方式及其简单,简单示例如下:

//注入spring中配置的SqlSessionTemplate对象,单例

@Resource(name="sqlSessionTemplate")

public SqlSessionTemplate sqlSessionTemplate;

public void saveTestTrans(){

this.sqlSessionTemplate.selectList("testdomain.selectAnySql", "select * from my_blog where id='1'");

}

这里的SqlSessionTemplate不仅是单例的,而且不需要手工新建和关闭SqlSession

问题1:

那么问题来了,为什么mybatis-spring.jar中的SqlSessionTemplate可以被多个dao复用,而且不会造成数据连接泄露呢,并且还可以自动新建和释放数据库连接?官方解答是因为SqlSessionTemplate是线程安全的,也就是确保每个线程使用的sqlSession的唯一并不互相冲突。

首先看了一下mybatis-spring的源码,发现SqlSessionTemplate是通过代理拦截和SqlSessionHolder实现的sqlsession线程安全和自动新建和释放连接的。看构造函数函数中构建代理类,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法,这个方法中自动进行了SqlSession的自动请求和释放(如果不被spring托管则自己新建和释放sqlsession,如果被spring管理则使用SqlSessionHolder进行request和relase操作)

以下网址针对SqlSessionTemplate的线程安全特性进行了详细的探究:

问题2:

然后又想到这样一个问题,虽然现在几乎所有项目都使用spring作为java程序的基本框架,如果我不使用spring管理mybatis,仅仅使用原始的mybatis,怎么样才能构建一个和SqlSessionTemplate相似的对象呢?

首先想到必须使用java的treadLocal构建一个sqlsession的对象,如ThreadLocal sqlSession = new ThreadLocal

()。

经过查找,发现mybatis自身就有这样一个类实现了类似的功能,类路径:org.apache.ibatis.session.SqlSessionManager,但是没有注释,可能存在mybatis-spring这种神器之后,mybatis放弃了对这个类的维护。

该类实现了SqlSessionFactory, SqlSession并且在其中定义了一个treadLocal的sqlssion对象,同时使用了代理拦截进行了sqlsession的自动管理,具体代码可以自己查阅,对于理解mybatis原理和java的代理机制很有帮助。

那么写个简单的程序验证一下SqlSessionManager是否真的可以保证线程安全和自动新建和释放sqlssion:TestSqlManager.java

private static SqlSession sqlSession;

public static SqlSession getSqlSessionTest(){

if(sqlSession == null){

//构建使用的SqlSessionFactory

SqlSessionFactory sqlSessionFactory = MyBatisUtil.getSqlSessionFactory();

sqlSession = SqlSessionManager.newInstance(sqlSessionFactory);

}

return sqlSession;

}

public static void main(String[] args) throws InterruptedException {

Run run = new Run();

List

threads = new ArrayList

();

for (int i = 0; i 100; i++) {

Thread t = new Thread(run);

threads.add(t);

System.out.println("thread:{"+t.getName()+"}, start");

t.start();

}

for (Thread t : threads) {

System.out.println("thread:{"+t.getName()+"},join");

t.join();

}

}

我本机装的mysql,通过监控语句:select SUBSTRING_INDEX(host,’:’,1) as ip , count(*) from information_schema.processlist group by ip;发现执行过程中存在连接并发的情况,但是执行之后全部释放掉了。

mybatis中怎么创建sqlsession

首先, 通过翻阅源码,我们来整理一下mybatis进行持久化操作时重要的几个类:

SqlSessionFactoryBuilder:build方法创建SqlSessionFactory实例。

SqlSessionFactory:创建SqlSession实例的工厂。

SqlSession:用于执行持久化操作的对象,类似于jdbc中的Connection。

SqlSessionTemplate:MyBatis提供的持久层访问模板化的工具,线程安全,可通过构造参数或依赖注入SqlSessionFactory实例。

Hibernate是与MyBatis类似的orm框架,这里与Hibernate进行一下对比,Hibernate中对于connection的管理,是通过以下几个重要的类:

SessionFactory:创建Session实例的工厂,类似于MyBatis中的SqlSessionFactory。

Session:用来执行持久化操作的对象,类似于jdbc中的Connection。

HibernateTemplate:Hibernate提供的持久层访问模板化的工具,线程安全,可通过构造参数或依赖注入SessionFactory实例。

在日常的开发中,我们经常需要这样对MyBatis和Spring进行集成,把sqlSessionFactory交给Spring管理,通常情况下,我们这样配置:

code class="335b-3d3a-426b-d151 hljs nimrod"bean id=span class="2c0d-bd37-c2b1-6635 hljs-string""sqlSessionFactory" class=span class="bd37-c2b1-6635-ae09 hljs-string""org.mybatis.spring.SqlSessionFactoryBean"

property name=span class="c2b1-6635-ae09-69da hljs-string""dataSource" span class="6635-ae09-69da-3b41 hljs-keyword"ref=span class="ae09-69da-3b41-9deb hljs-string""dataSource" /

/bean/span/span/span/span/span/code

通过上面的配置,Spring将自动创建一个SqlSessionFactory对象,其中使用到了org.mybatis.spring.SqlSessionFactoryBean,其 是MyBatis为Spring提供的用于创建SqlSessionFactory的类,将在Spring应用程序的上下文建议一下可共享的 MyBatis SqlSessionFactory实例,我们可以通过依赖注入将SqlSessionFactory传递给MyBatis的一些接口。

如果通过Spring进行事务的管理,我们需要增加Spring注解的事务管理机制,如下配置:

code class="69da-3b41-9deb-6393 hljs nimrod"bean id=span class="3b41-9deb-6393-e41f hljs-string""transactionManager" class=span class="9deb-6393-e41f-b3f4 hljs-string""org.springframework.jdbc.datasource.DataSourceTransactionManager"

property name=span class="6393-e41f-b3f4-99fe hljs-string""dataSource" span class="e41f-b3f4-99fe-eca4 hljs-keyword"ref=span class="b3f4-99fe-eca4-bc9a hljs-string""dataSource" /

/bean

tx:annotation-driven//span/span/span/span/span/code

这样,我们就可以使用Spring @Transactional注解,进行事务的控制,表明所注释的方法应该在一个事务中运行。 Spring将在事务成功完成后提交事务,在事务发生错误时进行异常回滚,而且,Spring会将产生的MyBatis异常转换成适当的 DataAccessExceptions,从而提供具体的异常信息。

下面,我们通过分析SqlSessionUtils中getSession的源码,来详细的了解一下sqlSession的产生过程,源码如下:

code class="99fe-eca4-bc9a-335b hljs d"span class="eca4-bc9a-335b-3d3a hljs-keyword"public span class="bc9a-335b-3d3a-426b hljs-keyword"static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, span class="335b-3d3a-426b-d151 hljs-string""No SqlSessionFactory specified");

notNull(executorType, span class="2c0d-bd37-c2b1-6635 hljs-string""No ExecutorType specified");

SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);

span class="bd37-c2b1-6635-ae09 hljs-keyword"if (holder != span class="c2b1-6635-ae09-69da hljs-literal"null holder.isSynchronizedWithTransaction()) {

span class="6635-ae09-69da-3b41 hljs-keyword"if (holder.getExecutorType() != executorType) {

span class="ae09-69da-3b41-9deb hljs-keyword"throw span class="69da-3b41-9deb-6393 hljs-keyword"new TransientDataAccessResourceException(span class="3b41-9deb-6393-e41f hljs-string""Cannot change the ExecutorType when there is an existing transaction");

}

holder.requested();

span class="9deb-6393-e41f-b3f4 hljs-keyword"if (logger.isDebugEnabled()) {

logger.span class="6393-e41f-b3f4-99fe hljs-keyword"debug(span class="e41f-b3f4-99fe-eca4 hljs-string""Fetched SqlSession [" + holder.getSqlSession() + span class="b3f4-99fe-eca4-bc9a hljs-string""] from current transaction");

}

span class="99fe-eca4-bc9a-335b hljs-keyword"return holder.getSqlSession();

}

span class="eca4-bc9a-335b-3d3a hljs-keyword"if (logger.isDebugEnabled()) {

logger.span class="bc9a-335b-3d3a-426b hljs-keyword"debug(span class="335b-3d3a-426b-d151 hljs-string""Creating a new SqlSession");

}

SqlSession session = sessionFactory.openSession(executorType);

span class="2c0d-bd37-c2b1-6635 hljs-comment"// Register session holder if synchronization is active (i.e. a Spring TX is active)

span class="bd37-c2b1-6635-ae09 hljs-comment"//

span class="c2b1-6635-ae09-69da hljs-comment"// Note: The DataSource used by the Environment should be synchronized with the

span class="6635-ae09-69da-3b41 hljs-comment"// transaction either through DataSourceTxMgr or another tx synchronization.

span class="ae09-69da-3b41-9deb hljs-comment"// Further assume that if an exception is thrown, whatever started the transaction will

span class="69da-3b41-9deb-6393 hljs-comment"// handle closing / rolling back the Connection associated with the SqlSession.

span class="3b41-9deb-6393-e41f hljs-keyword"if (isSynchronizationActive()) {

Environment environment = sessionFactory.getConfiguration().getEnvironment();

span class="9deb-6393-e41f-b3f4 hljs-keyword"if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {

span class="6393-e41f-b3f4-99fe hljs-keyword"if (logger.isDebugEnabled()) {

logger.span class="e41f-b3f4-99fe-eca4 hljs-keyword"debug(span class="b3f4-99fe-eca4-bc9a hljs-string""Registering transaction synchronization for SqlSession [" + session + span class="99fe-eca4-bc9a-335b hljs-string""]");

}

holder = span class="eca4-bc9a-335b-3d3a hljs-keyword"new SqlSessionHolder(session, executorType, exceptionTranslator);

bindResource(sessionFactory, holder);

registerSynchronization(span class="bc9a-335b-3d3a-426b hljs-keyword"new SqlSessionSynchronization(holder, sessionFactory));

holder.setSynchronizedWithTransaction(span class="335b-3d3a-426b-d151 hljs-literal"true);

holder.requested();

} span class="2c0d-bd37-c2b1-6635 hljs-keyword"else {

span class="bd37-c2b1-6635-ae09 hljs-keyword"if (getResource(environment.getDataSource()) == span class="c2b1-6635-ae09-69da hljs-literal"null) {

span class="6635-ae09-69da-3b41 hljs-keyword"if (logger.isDebugEnabled()) {

logger.span class="ae09-69da-3b41-9deb hljs-keyword"debug(span class="69da-3b41-9deb-6393 hljs-string""SqlSession [" + session + span class="3b41-9deb-6393-e41f hljs-string""] was not registered for synchronization because DataSource is not transactional");

}

} span class="9deb-6393-e41f-b3f4 hljs-keyword"else {

span class="6393-e41f-b3f4-99fe hljs-keyword"throw span class="e41f-b3f4-99fe-eca4 hljs-keyword"new TransientDataAccessResourceException(

span class="b3f4-99fe-eca4-bc9a hljs-string""SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");

}

}

} span class="99fe-eca4-bc9a-335b hljs-keyword"else {

span class="eca4-bc9a-335b-3d3a hljs-keyword"if (logger.isDebugEnabled()) {

logger.span class="bc9a-335b-3d3a-426b hljs-keyword"debug(span class="335b-3d3a-426b-d151 hljs-string""SqlSession [" + session + span class="2c0d-bd37-c2b1-6635 hljs-string""] was not registered for synchronization because synchronization is not active");

}

}

span class="bd37-c2b1-6635-ae09 hljs-keyword"return session;

}/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/span/code

1条大神的评论

  • avatar
    访客 2022-07-15 上午 07:29:18

    lSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else { Object param = method

发表评论