文章目录:
- 1、怎么没有mybatis源码解析相关的文档
- 2、如何使用SqlSessionTemplate
- 3、mybatis sqlSession.getMaper方法的返回类型
- 4、初看Mybatis 源码 SQL是怎么执行的
- 5、如何实现mybatis的sqlsessiontemplate
- 6、mybatis中怎么创建sqlsession
怎么没有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
lSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else { Object param = method